diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
commit | 66cec45960ce1d9c794e9399de15c138acb18aed (patch) | |
tree | 59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/cisco/nxos/plugins | |
parent | Initial commit. (diff) | |
download | ansible-66cec45960ce1d9c794e9399de15c138acb18aed.tar.xz ansible-66cec45960ce1d9c794e9399de15c138acb18aed.zip |
Adding upstream version 7.3.0+dfsg.upstream/7.3.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/nxos/plugins')
400 files changed, 88627 insertions, 0 deletions
diff --git a/ansible_collections/cisco/nxos/plugins/action/__init__.py b/ansible_collections/cisco/nxos/plugins/action/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/action/aaa_server.py b/ansible_collections/cisco/nxos/plugins/action/aaa_server.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/aaa_server.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/aaa_server_host.py b/ansible_collections/cisco/nxos/plugins/action/aaa_server_host.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/aaa_server_host.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/acl.py b/ansible_collections/cisco/nxos/plugins/action/acl.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/acl.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/acl_interface.py b/ansible_collections/cisco/nxos/plugins/action/acl_interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/acl_interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/acl_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/acl_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/acls.py b/ansible_collections/cisco/nxos/plugins/action/acls.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/acls.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/banner.py b/ansible_collections/cisco/nxos/plugins/action/banner.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/banner.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bfd_global.py b/ansible_collections/cisco/nxos/plugins/action/bfd_global.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bfd_global.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/bfd_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bfd_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp.py b/ansible_collections/cisco/nxos/plugins/action/bgp.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bgp.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/action/bgp_address_family.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bgp_address_family.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_af.py b/ansible_collections/cisco/nxos/plugins/action/bgp_af.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bgp_af.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_global.py b/ansible_collections/cisco/nxos/plugins/action/bgp_global.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bgp_global.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor.py b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_address_family.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_address_family.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_af.py b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_af.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_af.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/command.py b/ansible_collections/cisco/nxos/plugins/action/command.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/command.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/config.py b/ansible_collections/cisco/nxos/plugins/action/config.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/config.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/devicealias.py b/ansible_collections/cisco/nxos/plugins/action/devicealias.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/devicealias.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/evpn_global.py b/ansible_collections/cisco/nxos/plugins/action/evpn_global.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/evpn_global.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/evpn_vni.py b/ansible_collections/cisco/nxos/plugins/action/evpn_vni.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/evpn_vni.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/facts.py b/ansible_collections/cisco/nxos/plugins/action/facts.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/facts.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/feature.py b/ansible_collections/cisco/nxos/plugins/action/feature.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/feature.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/file_copy.py b/ansible_collections/cisco/nxos/plugins/action/file_copy.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/file_copy.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/gir.py b/ansible_collections/cisco/nxos/plugins/action/gir.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/gir.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/gir_profile_management.py b/ansible_collections/cisco/nxos/plugins/action/gir_profile_management.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/gir_profile_management.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/hostname.py b/ansible_collections/cisco/nxos/plugins/action/hostname.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/hostname.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/hsrp.py b/ansible_collections/cisco/nxos/plugins/action/hsrp.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/hsrp.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/hsrp_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/hsrp_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/igmp.py b/ansible_collections/cisco/nxos/plugins/action/igmp.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/igmp.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/igmp_interface.py b/ansible_collections/cisco/nxos/plugins/action/igmp_interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/igmp_interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/igmp_snooping.py b/ansible_collections/cisco/nxos/plugins/action/igmp_snooping.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/igmp_snooping.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/install_os.py b/ansible_collections/cisco/nxos/plugins/action/install_os.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/install_os.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/interface.py b/ansible_collections/cisco/nxos/plugins/action/interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/interface_ospf.py b/ansible_collections/cisco/nxos/plugins/action/interface_ospf.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/interface_ospf.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/interfaces.py b/ansible_collections/cisco/nxos/plugins/action/interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/l2_interface.py b/ansible_collections/cisco/nxos/plugins/action/l2_interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/l2_interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/l2_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/l2_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/l3_interface.py b/ansible_collections/cisco/nxos/plugins/action/l3_interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/l3_interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/l3_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/l3_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/lacp.py b/ansible_collections/cisco/nxos/plugins/action/lacp.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/lacp.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/lacp_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/lacp_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/lag_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/lag_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/linkagg.py b/ansible_collections/cisco/nxos/plugins/action/linkagg.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/linkagg.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/lldp.py b/ansible_collections/cisco/nxos/plugins/action/lldp.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/lldp.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/lldp_global.py b/ansible_collections/cisco/nxos/plugins/action/lldp_global.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/lldp_global.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/lldp_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/lldp_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/logging.py b/ansible_collections/cisco/nxos/plugins/action/logging.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/logging.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/logging_global.py b/ansible_collections/cisco/nxos/plugins/action/logging_global.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/logging_global.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ntp.py b/ansible_collections/cisco/nxos/plugins/action/ntp.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ntp.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ntp_auth.py b/ansible_collections/cisco/nxos/plugins/action/ntp_auth.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ntp_auth.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ntp_global.py b/ansible_collections/cisco/nxos/plugins/action/ntp_global.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ntp_global.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ntp_options.py b/ansible_collections/cisco/nxos/plugins/action/ntp_options.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ntp_options.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/nxapi.py b/ansible_collections/cisco/nxos/plugins/action/nxapi.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/nxapi.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/nxos.py b/ansible_collections/cisco/nxos/plugins/action/nxos.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/nxos.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ospf.py b/ansible_collections/cisco/nxos/plugins/action/ospf.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ospf.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/ospf_interfaces.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ospf_interfaces.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ospf_vrf.py b/ansible_collections/cisco/nxos/plugins/action/ospf_vrf.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ospf_vrf.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ospfv2.py b/ansible_collections/cisco/nxos/plugins/action/ospfv2.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ospfv2.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ospfv3.py b/ansible_collections/cisco/nxos/plugins/action/ospfv3.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ospfv3.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/overlay_global.py b/ansible_collections/cisco/nxos/plugins/action/overlay_global.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/overlay_global.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/pim.py b/ansible_collections/cisco/nxos/plugins/action/pim.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/pim.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/pim_interface.py b/ansible_collections/cisco/nxos/plugins/action/pim_interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/pim_interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/pim_rp_address.py b/ansible_collections/cisco/nxos/plugins/action/pim_rp_address.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/pim_rp_address.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/ping.py b/ansible_collections/cisco/nxos/plugins/action/ping.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/ping.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/action/prefix_lists.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/prefix_lists.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/reboot.py b/ansible_collections/cisco/nxos/plugins/action/reboot.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/reboot.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/rollback.py b/ansible_collections/cisco/nxos/plugins/action/rollback.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/rollback.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/route_maps.py b/ansible_collections/cisco/nxos/plugins/action/route_maps.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/route_maps.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/rpm.py b/ansible_collections/cisco/nxos/plugins/action/rpm.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/rpm.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/smu.py b/ansible_collections/cisco/nxos/plugins/action/smu.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/smu.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/snapshot.py b/ansible_collections/cisco/nxos/plugins/action/snapshot.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/snapshot.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_community.py b/ansible_collections/cisco/nxos/plugins/action/snmp_community.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/snmp_community.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_contact.py b/ansible_collections/cisco/nxos/plugins/action/snmp_contact.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/snmp_contact.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_host.py b/ansible_collections/cisco/nxos/plugins/action/snmp_host.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/snmp_host.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_location.py b/ansible_collections/cisco/nxos/plugins/action/snmp_location.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/snmp_location.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_server.py b/ansible_collections/cisco/nxos/plugins/action/snmp_server.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/snmp_server.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_traps.py b/ansible_collections/cisco/nxos/plugins/action/snmp_traps.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/snmp_traps.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_user.py b/ansible_collections/cisco/nxos/plugins/action/snmp_user.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/snmp_user.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/static_route.py b/ansible_collections/cisco/nxos/plugins/action/static_route.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/static_route.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/static_routes.py b/ansible_collections/cisco/nxos/plugins/action/static_routes.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/static_routes.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/system.py b/ansible_collections/cisco/nxos/plugins/action/system.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/system.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/telemetry.py b/ansible_collections/cisco/nxos/plugins/action/telemetry.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/telemetry.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/udld.py b/ansible_collections/cisco/nxos/plugins/action/udld.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/udld.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/udld_interface.py b/ansible_collections/cisco/nxos/plugins/action/udld_interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/udld_interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/user.py b/ansible_collections/cisco/nxos/plugins/action/user.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/user.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vlan.py b/ansible_collections/cisco/nxos/plugins/action/vlan.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vlan.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vlans.py b/ansible_collections/cisco/nxos/plugins/action/vlans.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vlans.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vpc.py b/ansible_collections/cisco/nxos/plugins/action/vpc.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vpc.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vpc_interface.py b/ansible_collections/cisco/nxos/plugins/action/vpc_interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vpc_interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vrf.py b/ansible_collections/cisco/nxos/plugins/action/vrf.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vrf.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vrf_af.py b/ansible_collections/cisco/nxos/plugins/action/vrf_af.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vrf_af.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vrf_interface.py b/ansible_collections/cisco/nxos/plugins/action/vrf_interface.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vrf_interface.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vrrp.py b/ansible_collections/cisco/nxos/plugins/action/vrrp.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vrrp.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vsan.py b/ansible_collections/cisco/nxos/plugins/action/vsan.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vsan.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vtp_domain.py b/ansible_collections/cisco/nxos/plugins/action/vtp_domain.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vtp_domain.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vtp_password.py b/ansible_collections/cisco/nxos/plugins/action/vtp_password.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vtp_password.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vtp_version.py b/ansible_collections/cisco/nxos/plugins/action/vtp_version.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vtp_version.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep.py b/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep_vni.py b/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep_vni.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep_vni.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/action/zone_zoneset.py b/ansible_collections/cisco/nxos/plugins/action/zone_zoneset.py new file mode 100644 index 00000000..3602c407 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/action/zone_zoneset.py @@ -0,0 +1,125 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible import constants as C +from ansible.module_utils.connection import Connection +from ansible.utils.display import Display +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = True if module_name in ["nxos_config", "config"] else False + persistent_connection = self._play_context.connection.split(".")[-1] + + warnings = [] + + if (self._play_context.connection == "httpapi") and module_name in ( + "nxos_file_copy", + "nxos_nxapi", + ): + return { + "failed": True, + "msg": "Connection httpapi is not valid for '%s' module." % (module_name), + } + + if module_name == "nxos_file_copy": + # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options + # will override persistent_command_timeout and port + # this has been kept for backwards compatibility till these options are removed + if persistent_connection != "network_cli": + return { + "failed": True, + "msg": "Connection type must be fully qualified name for network_cli connection type, got %s" + % self._play_context.connection, + } + + conn = Connection(self._connection.socket_path) + file_pull = self._task.args.get("file_pull", False) + file_pull_timeout = self._task.args.get("file_pull_timeout", 300) + connect_ssh_port = self._task.args.get("connect_ssh_port", 22) + + if file_pull: + conn.set_option("persistent_command_timeout", file_pull_timeout) + conn.set_option("port", connect_ssh_port) + + if module_name == "nxos_install_os": + connection = self._connection + if connection.transport == "local": + persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT + persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT + else: + persistent_command_timeout = connection.get_option("persistent_command_timeout") + persistent_connect_timeout = connection.get_option("persistent_connect_timeout") + + display.vvvv( + "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout), + self._play_context.remote_addr, + ) + display.vvvv( + "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout), + self._play_context.remote_addr, + ) + if persistent_command_timeout < 600 or persistent_connect_timeout < 600: + msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT" + msg += " must be set to 600 seconds or higher when using nxos_install_os module." + msg += " Current persistent_command_timeout setting:" + str( + persistent_command_timeout, + ) + msg += " Current persistent_connect_timeout setting:" + str( + persistent_connect_timeout, + ) + return {"failed": True, "msg": msg} + + if persistent_connection in ("network_cli", "httpapi"): + if module_name == "nxos_gir": + conn = Connection(self._connection.socket_path) + persistent_command_timeout = conn.get_option("persistent_command_timeout") + gir_timeout = 200 + if persistent_command_timeout < gir_timeout: + conn.set_option("persistent_command_timeout", gir_timeout) + msg = "timeout value extended to %ss for nxos_gir" % gir_timeout + display.warning(msg) + + else: + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/nxos/plugins/cliconf/__init__.py b/ansible_collections/cisco/nxos/plugins/cliconf/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/cliconf/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/cliconf/nxos.py b/ansible_collections/cisco/nxos/plugins/cliconf/nxos.py new file mode 100644 index 00000000..1b3bd909 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/cliconf/nxos.py @@ -0,0 +1,424 @@ +# +# (c) 2017 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 = """ +author: Ansible Networking Team (@ansible-network) +name: nxos +short_description: Use NX-OS cliconf to run commands on Cisco NX-OS platform +description: +- This nxos plugin provides low level abstraction apis for sending and receiving CLI + commands from Cisco NX-OS network devices. +version_added: 1.0.0 +options: + config_commands: + description: + - Specifies a list of commands that can make configuration changes + to the target device. + - When `ansible_network_single_user_mode` is enabled, if a command sent + to the device is present in this list, the existing cache is invalidated. + version_added: 2.0.0 + type: list + elements: str + default: [] + vars: + - name: ansible_nxos_config_commands +""" + +import json +import re + +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.module_utils.connection import ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, + dumps, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.ansible.netcommon.plugins.plugin_utils.cliconf_base import CliconfBase + + +class Cliconf(CliconfBase): + def __init__(self, *args, **kwargs): + self._module_context = {} + self._device_info = {} + super(Cliconf, self).__init__(*args, **kwargs) + + def read_module_context(self, module_key): + if self._module_context.get(module_key): + return self._module_context[module_key] + + return None + + def save_module_context(self, module_key, module_context): + self._module_context[module_key] = module_context + + return None + + def get_device_info(self): + if not self._device_info: + device_info = {} + + device_info["network_os"] = "nxos" + reply = self.get("show version") + platform_reply = self.get("show inventory") + + match_sys_ver = re.search(r"\s+system:\s+version\s*(\S+)", reply, re.M) + if match_sys_ver: + device_info["network_os_version"] = match_sys_ver.group(1) + else: + match_kick_ver = re.search(r"\s+kickstart:\s+version\s*(\S+)", reply, re.M) + if match_kick_ver: + device_info["network_os_version"] = match_kick_ver.group(1) + + if "network_os_version" not in device_info: + match_sys_ver = re.search(r"\s+NXOS:\s+version\s*(\S+)", reply, re.M) + if match_sys_ver: + device_info["network_os_version"] = match_sys_ver.group(1) + + match_chassis_id = re.search(r"Hardware\n\s+cisco(.+)$", reply, re.M) + if match_chassis_id: + device_info["network_os_model"] = match_chassis_id.group(1).strip() + + match_host_name = re.search(r"\s+Device name:\s*(\S+)", reply, re.M) + if match_host_name: + device_info["network_os_hostname"] = match_host_name.group(1) + + match_isan_file_name = re.search(r"\s+system image file is:\s*(\S+)", reply, re.M) + if match_isan_file_name: + device_info["network_os_image"] = match_isan_file_name.group(1) + else: + match_kick_file_name = re.search( + r"\s+kickstart image file is:\s*(\S+)", + reply, + re.M, + ) + if match_kick_file_name: + device_info["network_os_image"] = match_kick_file_name.group(1) + + if "network_os_image" not in device_info: + match_isan_file_name = re.search(r"\s+NXOS image file is:\s*(\S+)", reply, re.M) + if match_isan_file_name: + device_info["network_os_image"] = match_isan_file_name.group(1) + + match_os_platform = re.search( + r'NAME: "Chassis",\s*DESCR:.*\nPID:\s*(\S+)', + platform_reply, + re.M, + ) + if match_os_platform: + device_info["network_os_platform"] = match_os_platform.group(1) + + self._device_info = device_info + + return self._device_info + + def get_diff( + self, + candidate=None, + running=None, + diff_match="line", + diff_ignore_lines=None, + path=None, + diff_replace="line", + ): + diff = {} + device_operations = self.get_device_operations() + option_values = self.get_option_values() + + if candidate is None and device_operations["supports_generate_diff"]: + raise ValueError("candidate configuration is required to generate diff") + + if diff_match not in option_values["diff_match"]: + raise ValueError( + "'match' value %s in invalid, valid values are %s" + % (diff_match, ", ".join(option_values["diff_match"])), + ) + + if diff_replace not in option_values["diff_replace"]: + raise ValueError( + "'replace' value %s in invalid, valid values are %s" + % (diff_replace, ", ".join(option_values["diff_replace"])), + ) + + # prepare candidate configuration + candidate_obj = NetworkConfig(indent=2) + candidate_obj.load(candidate) + + if running and diff_match != "none" and diff_replace != "config": + # running configuration + running_obj = NetworkConfig(indent=2, contents=running, ignore_lines=diff_ignore_lines) + configdiffobjs = candidate_obj.difference( + running_obj, + path=path, + match=diff_match, + replace=diff_replace, + ) + + else: + configdiffobjs = candidate_obj.items + + diff["config_diff"] = dumps(configdiffobjs, "commands") if configdiffobjs else "" + return diff + + def get_config(self, source="running", format="text", flags=None): + options_values = self.get_option_values() + if format not in options_values["format"]: + raise ValueError( + "'format' value %s is invalid. Valid values are %s" + % (format, ",".join(options_values["format"])), + ) + + lookup = {"running": "running-config", "startup": "startup-config"} + if source not in lookup: + raise ValueError("fetching configuration from %s is not supported" % source) + + cmd = "show {0} ".format(lookup[source]) + if format and format != "text": + cmd += "| %s " % format + + if flags: + cmd += " ".join(to_list(flags)) + cmd = cmd.strip() + + return self.send_command(cmd) + + def edit_config(self, candidate=None, commit=True, replace=None, comment=None): + resp = {} + operations = self.get_device_operations() + self.check_edit_config_capability(operations, candidate, commit, replace, comment) + results = [] + requests = [] + + if replace: + device_info = self.get_device_info() + # not all NX-OS versions support `config replace` + # we let the device throw the invalid command error + candidate = "config replace {0}".format(replace) + + if commit: + self.send_command("configure terminal") + + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {"command": line} + + cmd = line["command"] + if cmd != "end": + results.append(self.send_command(**line)) + requests.append(cmd) + + self.send_command("end") + else: + raise ValueError("check mode is not supported") + + resp["request"] = requests + resp["response"] = results + return resp + + def get( + self, + command, + prompt=None, + answer=None, + sendonly=False, + output=None, + newline=True, + check_all=False, + ): + if output: + command = self._get_command_with_output(command, output) + return self.send_command( + command=command, + prompt=prompt, + answer=answer, + sendonly=sendonly, + newline=newline, + check_all=check_all, + ) + + 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: + cmd["command"] = self._get_command_with_output(cmd["command"], output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc is True: + raise + out = getattr(e, "err", e) + + if out is not None: + try: + out = to_text(out, errors="surrogate_or_strict").strip() + except UnicodeError: + raise ConnectionError( + message="Failed to decode output from %s: %s" % (cmd, to_text(out)), + ) + + try: + out = json.loads(out) + except ValueError: + pass + + responses.append(out) + return responses + + def get_device_operations(self): + return { + "supports_diff_replace": True, + "supports_commit": False, + "supports_rollback": False, + "supports_defaults": True, + "supports_onbox_diff": False, + "supports_commit_comment": False, + "supports_multiline_delimiter": False, + "supports_diff_match": True, + "supports_diff_ignore_lines": True, + "supports_generate_diff": True, + "supports_replace": True, + } + + def get_option_values(self): + return { + "format": ["text", "json"], + "diff_match": ["line", "strict", "exact", "none"], + "diff_replace": ["line", "block", "config"], + "output": ["text", "json"], + } + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + result["rpc"] += ["get_diff", "run_commands"] + result["device_operations"] = self.get_device_operations() + result.update(self.get_option_values()) + + return json.dumps(result) + + def pull_file(self, command, remotepassword=None): + possible_errors_re = [ + re.compile(rb"timed out"), + re.compile(rb"(?i)No space.*#"), + re.compile(rb"(?i)Permission denied.*#"), + re.compile(rb"(?i)No such file.*#"), + re.compile(rb"Compaction is not supported on this platform.*#"), + re.compile(rb"Compact of.*failed.*#"), + re.compile(rb"(?i)Could not resolve hostname"), + re.compile(rb"(?i)Too many authentication failures"), + re.compile(rb"Access Denied"), + re.compile(rb"(?i)Copying to\/from this server name is not permitted"), + ] + + # set error regex for copy command + current_stderr_re = self._connection._get_terminal_std_re("terminal_stderr_re") + current_stderr_re.extend(possible_errors_re) + + # do not change the ordering of this list + possible_prompts_re = [ + re.compile(rb"file existing with this name"), + re.compile(rb"sure you want to continue connecting"), + re.compile(rb"(?i)Password:.*"), + ] + + # set stdout regex for copy command to handle optional user prompts + # based on different match conditions + current_stdout_re = self._connection._get_terminal_std_re("terminal_stdout_re") + current_stdout_re.extend(possible_prompts_re) + + retry = 1 + file_pulled = False + + try: + while not file_pulled and retry <= 6: + retry += 1 + output = self.send_command(command=command, strip_prompt=False) + + if possible_prompts_re[0].search(to_bytes(output)): + output = self.send_command(command="y", strip_prompt=False) + + if possible_prompts_re[1].search(to_bytes(output)): + output = self.send_command(command="yes", strip_prompt=False) + + if possible_prompts_re[2].search(to_bytes(output)): + output = self.send_command(command=remotepassword, strip_prompt=False) + if "Copy complete" in output: + file_pulled = True + return file_pulled + finally: + # always reset terminal regexes to default + for x in possible_prompts_re: + current_stdout_re.remove(x) + for x in possible_errors_re: + current_stderr_re.remove(x) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli context + :return: None + """ + if self._connection.connected: + out = self._connection.get_prompt() + if out is None: + raise AnsibleConnectionFailure( + message="cli prompt is not identified from the last received" + " response window: %s" % self._connection._last_recv_window, + ) + # Match prompts ending in )# except those with (maint-mode)# + config_prompt = re.compile(r"^.*\((?!maint-mode).*\)#$") + + while config_prompt.match(to_text(out, errors="surrogate_then_replace").strip()): + self._connection.queue_message("vvvv", "wrong context, sending exit to device") + self._connection.send_command("exit") + out = self._connection.get_prompt() + + def _get_command_with_output(self, command, output): + options_values = self.get_option_values() + if output not in options_values["output"]: + raise ValueError( + "'output' value %s is invalid. Valid values are %s" + % (output, ",".join(options_values["output"])), + ) + + if output == "json" and not command.endswith("| json"): + device_info = self.get_device_info() + model = device_info.get("network_os_model", "") + platform = device_info.get("network_os_platform", "") + if platform.startswith("DS-") and "MDS" in model: + cmd = "%s | json native" % command + else: + cmd = "%s | json" % command + elif output == "text" and command.endswith("| json"): + cmd = command.rsplit("|", 1)[0] + else: + cmd = command + return cmd diff --git a/ansible_collections/cisco/nxos/plugins/doc_fragments/__init__.py b/ansible_collections/cisco/nxos/plugins/doc_fragments/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/doc_fragments/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/doc_fragments/nxos.py b/ansible_collections/cisco/nxos/plugins/doc_fragments/nxos.py new file mode 100644 index 00000000..d3c6afe5 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/doc_fragments/nxos.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + # Standard files documentation fragment + DOCUMENTATION = r"""options: {} +notes: + - For information on using CLI and NX-API see the :ref:`NXOS Platform Options guide + <nxos_platform_options>` + - For more information on using Ansible to manage network devices see the :ref:`Ansible + Network Guide <network_guide>` + - For more information on using Ansible to manage Cisco devices see the `Cisco integration + page <https://www.ansible.com/integrations/networks/cisco>`_. +""" diff --git a/ansible_collections/cisco/nxos/plugins/filter/__init__.py b/ansible_collections/cisco/nxos/plugins/filter/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/filter/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/httpapi/__init__.py b/ansible_collections/cisco/nxos/plugins/httpapi/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/httpapi/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/httpapi/nxos.py b/ansible_collections/cisco/nxos/plugins/httpapi/nxos.py new file mode 100644 index 00000000..57421e83 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/httpapi/nxos.py @@ -0,0 +1,266 @@ +# (c) 2018 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +author: Ansible Networking Team (@ansible-network) +name: nxos +short_description: Use NX-API to run commands on Cisco NX-OS platform +description: +- This plugin provides low level abstraction APIs for sending and receiving + commands using NX-API with devices running Cisco NX-OS. +version_added: 1.0.0 +""" + +import collections +import json +import re + +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.ansible.netcommon.plugins.plugin_utils.httpapi_base import HttpApiBase + + +OPTIONS = { + "format": ["text", "json"], + "diff_match": ["line", "strict", "exact", "none"], + "diff_replace": ["line", "block", "config"], + "output": ["text", "json"], +} + + +class HttpApi(HttpApiBase): + def __init__(self, *args, **kwargs): + super(HttpApi, self).__init__(*args, **kwargs) + self._device_info = None + self._module_context = {} + + def read_module_context(self, module_key): + if self._module_context.get(module_key): + return self._module_context[module_key] + + return None + + def save_module_context(self, module_key, module_context): + self._module_context[module_key] = module_context + + return None + + def send_request(self, data, **message_kwargs): + output = None + queue = list() + responses = list() + + for item in to_list(data): + cmd_output = message_kwargs.get("output") or "text" + if isinstance(item, dict): + command = item["command"] + if "output" in item: + cmd_output = item["output"] + else: + command = item + + # Emulate '| json' from CLI + if command.endswith("| json"): + command = command.rsplit("|", 1)[0] + cmd_output = "json" + + if output and output != cmd_output: + responses.extend(self._run_queue(queue, output)) + queue = list() + + output = cmd_output + queue.append(command) + + if queue: + responses.extend(self._run_queue(queue, output)) + + if len(responses) == 1: + return responses[0] + return responses + + def _run_queue(self, queue, output): + if self._become: + self.connection.queue_message( + "warning", + "become has no effect over httpapi. Use network_cli if privilege escalation is required", + ) + + request = request_builder(queue, output) + headers = {"Content-Type": "application/json"} + + response, response_data = self.connection.send( + "/ins", + request, + headers=headers, + method="POST", + ) + + try: + response_data = json.loads(to_text(response_data.getvalue())) + except ValueError: + raise ConnectionError( + "Response was not valid JSON, got {0}".format(to_text(response_data.getvalue())), + ) + + results = handle_response(response_data) + return results + + def get_device_info(self): + if self._device_info: + return self._device_info + + device_info = {} + + device_info["network_os"] = "nxos" + reply, platform_reply = self.send_request(["show version", "show inventory"]) + + find_os_version = [ + r"\s+system:\s+version\s*(\S+)", + r"\s+kickstart:\s+version\s*(\S+)", + r"\s+NXOS:\s+version\s*(\S+)", + ] + for regex in find_os_version: + match_ver = re.search(regex, reply, re.M) + if match_ver: + device_info["network_os_version"] = match_ver.group(1) + break + + match_chassis_id = re.search(r"Hardware\n\s+cisco\s*(\S+\s+\S+)", reply, re.M) + if match_chassis_id: + device_info["network_os_model"] = match_chassis_id.group(1) + + match_host_name = re.search(r"\s+Device name:\s*(\S+)", reply, re.M) + if match_host_name: + device_info["network_os_hostname"] = match_host_name.group(1) + + find_os_image = [ + r"\s+system image file is:\s*(\S+)", + r"\s+kickstart image file is:\s*(\S+)", + r"\s+NXOS image file is:\s*(\S+)", + ] + for regex in find_os_image: + match_file_name = re.search(regex, reply, re.M) + if match_file_name: + device_info["network_os_image"] = match_file_name.group(1) + break + + match_os_platform = re.search( + r'NAME: (?:"Chassis"| Chassis ),\s*DESCR:.*\nPID:\s*(\S+)', + platform_reply, + re.M, + ) + if match_os_platform: + device_info["network_os_platform"] = match_os_platform.group(1) + + self._device_info = device_info + return self._device_info + + def get_device_operations(self): + platform = self.get_device_info().get("network_os_platform", "") + return { + "supports_diff_replace": True, + "supports_commit": False, + "supports_rollback": False, + "supports_defaults": True, + "supports_onbox_diff": False, + "supports_commit_comment": False, + "supports_multiline_delimiter": False, + "supports_diff_match": True, + "supports_diff_ignore_lines": True, + "supports_generate_diff": True, + "supports_replace": True if "9K" in platform else False, + } + + def get_capabilities(self): + result = {} + result["rpc"] = [] + result["device_info"] = self.get_device_info() + result["device_operations"] = self.get_device_operations() + result.update(OPTIONS) + result["network_api"] = "nxapi" + + return json.dumps(result) + + # Shims for resource module support + def get(self, command, output=None): + # This method is ONLY here to support resource modules. Therefore most + # arguments are unsupported and not present. + + return self.send_request(data=command, output=output) + + def edit_config(self, candidate): + # This method is ONLY here to support resource modules. Therefore most + # arguments are unsupported and not present. + + responses = self.send_request(candidate, output="config") + return [resp for resp in to_list(responses) if resp != "{}"] + + +def handle_response(response): + results = [] + + if response["ins_api"].get("outputs"): + for output in to_list(response["ins_api"]["outputs"]["output"]): + if output["code"] != "200": + # Best effort messages: some API output keys may not exist on some platforms + input_data = output.get("input", "") + msg = output.get("msg", "") + clierror = output.get("clierror", "") + raise ConnectionError( + "%s: %s: %s" % (input_data, msg, clierror), + code=output["code"], + ) + elif "body" in output: + result = output["body"] + if isinstance(result, dict): + result = json.dumps(result) + + results.append(result.strip()) + + return results + + +def request_builder(commands, output, version="1.0", chunk="0", sid=None): + """Encodes a NXAPI JSON request message""" + output_to_command_type = { + "text": "cli_show_ascii", + "json": "cli_show", + "bash": "bash", + "config": "cli_conf", + } + + maybe_output = commands[0].split("|")[-1].strip() + if maybe_output in output_to_command_type: + command_type = output_to_command_type[maybe_output] + commands = [command.split("|")[0].strip() for command in commands] + else: + try: + command_type = output_to_command_type[output] + except KeyError: + msg = "invalid format, received %s, expected one of %s" % ( + output, + ",".join(output_to_command_type.keys()), + ) + raise ConnectionError(msg) + + if isinstance(commands, (list, set, tuple)): + commands = " ;".join(commands) + + # Order should not matter but some versions of NX-OS software fail + # to process the payload properly if 'input' gets serialized before + # 'type' and the payload of 'input' contains the word 'type'. + msg = collections.OrderedDict() + msg["version"] = version + msg["type"] = command_type + msg["chunk"] = chunk + msg["sid"] = sid + msg["input"] = commands + msg["output_format"] = "json" + + return json.dumps(dict(ins_api=msg)) diff --git a/ansible_collections/cisco/nxos/plugins/inventory/__init__.py b/ansible_collections/cisco/nxos/plugins/inventory/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/inventory/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000..ad2d59d3 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py @@ -0,0 +1,82 @@ +# +# -*- 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 nxos_acl_interfaces module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Acl_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_acl_interfaces module""" + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "access_groups": { + "elements": "dict", + "options": { + "acls": { + "elements": "dict", + "options": { + "direction": { + "choices": ["in", "out"], + "required": True, + "type": "str", + }, + "name": {"required": True, "type": "str"}, + "port": {"type": "bool"}, + }, + "type": "list", + }, + "afi": { + "choices": ["ipv4", "ipv6"], + "required": True, + "type": "str", + }, + }, + "type": "list", + }, + "name": {"required": True, "type": "str"}, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "deleted", + "gathered", + "merged", + "overridden", + "rendered", + "replaced", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/acls.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/acls.py new file mode 100644 index 00000000..3618248b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/acls.py @@ -0,0 +1,314 @@ +# +# -*- 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 nxos_acls module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class AclsArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_acls module""" + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "acls": { + "elements": "dict", + "options": { + "aces": { + "elements": "dict", + "mutually_exclusive": [["grant", "remark"]], + "options": { + "destination": { + "mutually_exclusive": [ + ["address", "any", "host", "prefix"], + [ + "wildcard_bits", + "any", + "host", + "prefix", + ], + ], + "options": { + "address": {"type": "str"}, + "any": {"type": "bool"}, + "host": {"type": "str"}, + "port_protocol": { + "mutually_exclusive": [ + [ + "eq", + "lt", + "neq", + "gt", + "range", + ], + ], + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "options": { + "end": {"type": "str"}, + "start": {"type": "str"}, + }, + "required_together": [["start", "end"]], + "type": "dict", + }, + }, + "type": "dict", + }, + "prefix": {"type": "str"}, + "wildcard_bits": {"type": "str"}, + }, + "required_together": [["address", "wildcard_bits"]], + "type": "dict", + }, + "dscp": {"type": "str"}, + "fragments": {"type": "bool"}, + "grant": { + "choices": ["permit", "deny"], + "type": "str", + }, + "log": {"type": "bool"}, + "precedence": {"type": "str"}, + "protocol": {"type": "str"}, + "protocol_options": { + "mutually_exclusive": [["icmp", "igmp", "tcp"]], + "options": { + "icmp": { + "options": { + "administratively_prohibited": {"type": "bool"}, + "alternate_address": {"type": "bool"}, + "conversion_error": {"type": "bool"}, + "dod_host_prohibited": {"type": "bool"}, + "dod_net_prohibited": {"type": "bool"}, + "echo": {"type": "bool"}, + "echo_reply": {"type": "bool"}, + "echo_request": {"type": "bool"}, + "general_parameter_problem": {"type": "bool"}, + "host_isolated": {"type": "bool"}, + "host_precedence_unreachable": {"type": "bool"}, + "host_redirect": {"type": "bool"}, + "host_tos_redirect": {"type": "bool"}, + "host_tos_unreachable": {"type": "bool"}, + "host_unknown": {"type": "bool"}, + "host_unreachable": {"type": "bool"}, + "information_reply": {"type": "bool"}, + "information_request": {"type": "bool"}, + "mask_reply": {"type": "bool"}, + "mask_request": {"type": "bool"}, + "message_code": {"type": "int"}, + "message_type": {"type": "int"}, + "mobile_redirect": {"type": "bool"}, + "net_redirect": {"type": "bool"}, + "net_tos_redirect": {"type": "bool"}, + "net_tos_unreachable": {"type": "bool"}, + "net_unreachable": {"type": "bool"}, + "network_unknown": {"type": "bool"}, + "no_room_for_option": {"type": "bool"}, + "option_missing": {"type": "bool"}, + "packet_too_big": {"type": "bool"}, + "parameter_problem": {"type": "bool"}, + "port_unreachable": {"type": "bool"}, + "precedence_unreachable": {"type": "bool"}, + "protocol_unreachable": {"type": "bool"}, + "reassembly_timeout": {"type": "bool"}, + "redirect": {"type": "bool"}, + "router_advertisement": {"type": "bool"}, + "router_solicitation": {"type": "bool"}, + "source_quench": {"type": "bool"}, + "source_route_failed": {"type": "bool"}, + "time_exceeded": {"type": "bool"}, + "timestamp_reply": {"type": "bool"}, + "timestamp_request": {"type": "bool"}, + "traceroute": {"type": "bool"}, + "ttl_exceeded": {"type": "bool"}, + "unreachable": {"type": "bool"}, + }, + "type": "dict", + }, + "icmpv6": { + "type": "dict", + "options": { + "beyond_scope": {"type": "bool"}, + "destination_unreachable": { + "type": "bool", + }, + "echo_reply": {"type": "bool"}, + "echo_request": {"type": "bool"}, + "fragments": {"type": "bool"}, + "header": {"type": "bool"}, + "hop_limit": {"type": "bool"}, + "mld_query": {"type": "bool"}, + "mld_reduction": {"type": "bool"}, + "mld_report": {"type": "bool"}, + "mldv2": {"type": "bool"}, + "nd_na": {"type": "bool"}, + "nd_ns": {"type": "bool"}, + "next_header": {"type": "bool"}, + "no_admin": {"type": "bool"}, + "no_route": {"type": "bool"}, + "packet_too_big": {"type": "bool"}, + "parameter_option": { + "type": "bool", + }, + "parameter_problem": { + "type": "bool", + }, + "port_unreachable": { + "type": "bool", + }, + "reassembly_timeout": { + "type": "bool", + }, + "renum_command": {"type": "bool"}, + "renum_result": {"type": "bool"}, + "renum_seq_number": { + "type": "bool", + }, + "router_advertisement": { + "type": "bool", + }, + "router_renumbering": { + "type": "bool", + }, + "router_solicitation": { + "type": "bool", + }, + "time_exceeded": {"type": "bool"}, + "unreachable": {"type": "bool"}, + "telemetry_path": {"type": "bool"}, + "telemetry_queue": { + "type": "bool", + }, + }, + }, + "igmp": { + "mutually_exclusive": [ + [ + "dvmrp", + "host_query", + "host_report", + ], + ], + "options": { + "dvmrp": {"type": "bool"}, + "host_query": {"type": "bool"}, + "host_report": {"type": "bool"}, + }, + "type": "dict", + }, + "tcp": { + "options": { + "ack": {"type": "bool"}, + "established": {"type": "bool"}, + "fin": {"type": "bool"}, + "psh": {"type": "bool"}, + "rst": {"type": "bool"}, + "syn": {"type": "bool"}, + "urg": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "remark": {"type": "str"}, + "sequence": {"type": "int"}, + "source": { + "mutually_exclusive": [ + ["address", "any", "host", "prefix"], + [ + "wildcard_bits", + "host", + "any", + "prefix", + ], + ], + "options": { + "address": {"type": "str"}, + "any": {"type": "bool"}, + "host": {"type": "str"}, + "port_protocol": { + "mutually_exclusive": [ + ["eq", "lt", "neq", "range"], + ["eq", "gt", "neq", "range"], + ], + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "options": { + "end": {"type": "str"}, + "start": {"type": "str"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "prefix": {"type": "str"}, + "wildcard_bits": {"type": "str"}, + }, + "required_together": [["address", "wildcard_bits"]], + "type": "dict", + }, + }, + "type": "list", + }, + "name": {"required": True, "type": "str"}, + }, + "type": "list", + }, + "afi": { + "choices": ["ipv4", "ipv6"], + "required": True, + "type": "str", + }, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "deleted", + "gathered", + "merged", + "overridden", + "rendered", + "replaced", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/bfd_interfaces.py new file mode 100644 index 00000000..6c47d2e5 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/bfd_interfaces.py @@ -0,0 +1,61 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# 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 + +""" +The arg spec for the nxos_bfd_interfaces module +""" + + +class Bfd_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_bfd_interfaces module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "elements": "dict", + "options": { + "name": {"type": "str"}, + "bfd": {"choices": ["enable", "disable"], "type": "str"}, + "echo": {"choices": ["enable", "disable"], "type": "str"}, + }, + "type": "list", + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/bgp_address_family.py new file mode 100644 index 00000000..f77843d7 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/bgp_address_family.py @@ -0,0 +1,250 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_bgp_address_family module +""" + + +class Bgp_address_familyArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_bgp_address_family module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "dict", + "options": { + "as_number": {"type": "str"}, + "address_family": { + "type": "list", + "elements": "dict", + "options": { + "afi": { + "type": "str", + "choices": [ + "ipv4", + "ipv6", + "link-state", + "vpnv4", + "vpnv6", + "l2vpn", + ], + "required": True, + }, + "safi": { + "type": "str", + "choices": [ + "unicast", + "multicast", + "mvpn", + "evpn", + ], + }, + "additional_paths": { + "type": "dict", + "options": { + "install_backup": {"type": "bool"}, + "receive": {"type": "bool"}, + "selection": { + "type": "dict", + "options": {"route_map": {"type": "str"}}, + }, + "send": {"type": "bool"}, + }, + }, + "advertise_l2vpn_evpn": {"type": "bool"}, + "advertise_pip": {"type": "bool"}, + "advertise_system_mac": {"type": "bool"}, + "allow_vni_in_ethertag": {"type": "bool"}, + "aggregate_address": { + "type": "list", + "elements": "dict", + "options": { + "prefix": {"type": "str"}, + "advertise_map": {"type": "str"}, + "as_set": {"type": "bool"}, + "attribute_map": {"type": "str"}, + "summary_only": {"type": "bool"}, + "suppress_map": {"type": "str"}, + }, + }, + "client_to_client": { + "type": "dict", + "options": {"no_reflection": {"type": "bool"}}, + }, + "dampen_igp_metric": {"type": "int"}, + "dampening": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "decay_half_life": {"type": "int"}, + "start_reuse_route": {"type": "int"}, + "start_suppress_route": {"type": "int"}, + "max_suppress_time": {"type": "int"}, + "route_map": {"type": "str"}, + }, + }, + "default_information": { + "type": "dict", + "options": {"originate": {"type": "bool"}}, + }, + "default_metric": {"type": "int"}, + "distance": { + "type": "dict", + "options": { + "ebgp_routes": {"type": "int"}, + "ibgp_routes": {"type": "int"}, + "local_routes": {"type": "int"}, + }, + }, + "export_gateway_ip": {"type": "bool"}, + "inject_map": { + "type": "list", + "elements": "dict", + "options": { + "route_map": {"type": "str"}, + "exist_map": {"type": "str"}, + "copy_attributes": {"type": "bool"}, + }, + }, + "maximum_paths": { + "type": "dict", + "options": { + "parallel_paths": {"type": "int"}, + "ibgp": { + "type": "dict", + "options": {"parallel_paths": {"type": "int"}}, + }, + "eibgp": { + "type": "dict", + "options": {"parallel_paths": {"type": "int"}}, + }, + "local": { + "type": "dict", + "options": {"parallel_paths": {"type": "int"}}, + }, + "mixed": { + "type": "dict", + "options": {"parallel_paths": {"type": "int"}}, + }, + }, + }, + "networks": { + "type": "list", + "elements": "dict", + "options": { + "prefix": {"type": "str"}, + "route_map": {"type": "str"}, + }, + }, + "nexthop": { + "type": "dict", + "options": { + "route_map": {"type": "str"}, + "trigger_delay": { + "type": "dict", + "options": { + "critical_delay": {"type": "int"}, + "non_critical_delay": {"type": "int"}, + }, + }, + }, + }, + "redistribute": { + "type": "list", + "elements": "dict", + "options": { + "protocol": { + "type": "str", + "choices": [ + "am", + "direct", + "eigrp", + "isis", + "lisp", + "ospf", + "ospfv3", + "rip", + "static", + "hmm", + ], + "required": True, + }, + "id": {"type": "str"}, + "route_map": {"type": "str", "required": True}, + }, + }, + "retain": { + "type": "dict", + "options": { + "route_target": { + "type": "dict", + "options": { + "retain_all": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + }, + }, + "suppress_inactive": {"type": "bool"}, + "table_map": { + "type": "dict", + "options": { + "name": {"type": "str", "required": True}, + "filter": {"type": "bool"}, + }, + }, + "timers": { + "type": "dict", + "options": { + "bestpath_defer": { + "type": "dict", + "options": { + "defer_time": {"type": "int"}, + "maximum_defer_time": {"type": "int"}, + }, + }, + }, + }, + "wait_igp_convergence": {"type": "bool"}, + "vrf": {"type": "str"}, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/bgp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/bgp_global.py new file mode 100644 index 00000000..d47f7e42 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/bgp_global.py @@ -0,0 +1,549 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_bgp_global module +""" + + +class Bgp_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_bgp_global module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "dict", + "options": { + "as_number": {"type": "str"}, + "affinity_group": { + "type": "dict", + "options": {"group_id": {"type": "int"}}, + }, + "bestpath": { + "type": "dict", + "options": { + "always_compare_med": {"type": "bool"}, + "as_path": { + "type": "dict", + "options": { + "ignore": {"type": "bool"}, + "multipath_relax": {"type": "bool"}, + }, + }, + "compare_neighborid": {"type": "bool"}, + "compare_routerid": {"type": "bool"}, + "cost_community_ignore": {"type": "bool"}, + "igp_metric_ignore": {"type": "bool"}, + "med": { + "type": "dict", + "options": { + "confed": {"type": "bool"}, + "missing_as_worst": {"type": "bool"}, + "non_deterministic": {"type": "bool"}, + }, + }, + }, + }, + "cluster_id": {"type": "str"}, + "confederation": { + "type": "dict", + "options": { + "identifier": {"type": "str"}, + "peers": {"type": "list", "elements": "str"}, + }, + }, + "disable_policy_batching": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "ipv4": { + "type": "dict", + "options": {"prefix_list": {"type": "str"}}, + }, + "ipv6": { + "type": "dict", + "options": {"prefix_list": {"type": "str"}}, + }, + "nexthop": {"type": "bool"}, + }, + }, + "dynamic_med_interval": {"type": "int"}, + "enforce_first_as": {"type": "bool"}, + "enhanced_error": {"type": "bool"}, + "fabric_soo": {"type": "str"}, + "fast_external_fallover": {"type": "bool"}, + "flush_routes": {"type": "bool"}, + "graceful_restart": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "restart_time": {"type": "int"}, + "stalepath_time": {"type": "int"}, + "helper": {"type": "bool"}, + }, + }, + "graceful_shutdown": { + "type": "dict", + "options": { + "activate": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + "aware": {"type": "bool"}, + }, + }, + "isolate": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "include_local": {"type": "bool"}, + }, + }, + "log_neighbor_changes": {"type": "bool"}, + "maxas_limit": {"type": "int"}, + "neighbors": { + "type": "list", + "elements": "dict", + "options": { + "neighbor_address": {"type": "str", "required": True}, + "bfd": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "singlehop": {"type": "bool"}, + "multihop": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "interval": { + "type": "dict", + "options": { + "tx_interval": {"type": "int"}, + "min_rx_interval": {"type": "int"}, + "multiplier": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "neighbor_affinity_group": { + "type": "dict", + "options": {"group_id": {"type": "int"}}, + }, + "bmp_activate_server": {"type": "int"}, + "capability": { + "type": "dict", + "options": {"suppress_4_byte_as": {"type": "bool"}}, + }, + "description": {"type": "str"}, + "disable_connected_check": {"type": "bool"}, + "dont_capability_negotiate": {"type": "bool"}, + "dscp": {"type": "str"}, + "dynamic_capability": {"type": "bool"}, + "ebgp_multihop": {"type": "int"}, + "graceful_shutdown": { + "type": "dict", + "options": { + "activate": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + }, + }, + "inherit": { + "type": "dict", + "options": { + "peer": {"type": "str"}, + "peer_session": {"type": "str"}, + }, + }, + "local_as": {"type": "str"}, + "log_neighbor_changes": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "disable": {"type": "bool"}, + }, + }, + "low_memory": { + "type": "dict", + "options": {"exempt": {"type": "bool"}}, + }, + "password": { + "type": "dict", + "no_log": False, + "options": { + "encryption": {"type": "int"}, + "key": {"type": "str", "no_log": True}, + }, + }, + "path_attribute": { + "type": "list", + "elements": "dict", + "options": { + "action": { + "type": "str", + "choices": [ + "discard", + "treat-as-withdraw", + ], + }, + "type": {"type": "int"}, + "range": { + "type": "dict", + "options": { + "start": {"type": "int"}, + "end": {"type": "int"}, + }, + }, + }, + }, + "peer_type": { + "type": "str", + "choices": [ + "fabric-border-leaf", + "fabric-external", + ], + }, + "remote_as": {"type": "str"}, + "remove_private_as": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "replace_as": {"type": "bool"}, + "all": {"type": "bool"}, + }, + }, + "shutdown": {"type": "bool"}, + "timers": { + "type": "dict", + "options": { + "keepalive": {"type": "int"}, + "holdtime": {"type": "int"}, + }, + }, + "transport": { + "type": "dict", + "options": { + "connection_mode": { + "type": "dict", + "options": {"passive": {"type": "bool"}}, + }, + }, + }, + "ttl_security": { + "type": "dict", + "options": {"hops": {"type": "int"}}, + }, + "update_source": {"type": "str"}, + }, + }, + "neighbor_down": { + "type": "dict", + "options": {"fib_accelerate": {"type": "bool"}}, + }, + "nexthop": { + "type": "dict", + "options": {"suppress_default_resolution": {"type": "bool"}}, + }, + "rd": { + "type": "dict", + "options": { + "dual": {"type": "bool"}, + "id": {"type": "int"}, + }, + }, + "reconnect_interval": {"type": "int"}, + "router_id": {"type": "str"}, + "shutdown": {"type": "bool"}, + "suppress_fib_pending": {"type": "bool"}, + "timers": { + "type": "dict", + "options": { + "bestpath_limit": { + "type": "dict", + "options": { + "timeout": {"type": "int"}, + "always": {"type": "bool"}, + }, + }, + "bgp": { + "type": "dict", + "options": { + "keepalive": {"type": "int"}, + "holdtime": {"type": "int"}, + }, + }, + "prefix_peer_timeout": {"type": "int"}, + "prefix_peer_wait": {"type": "int"}, + }, + }, + "vrfs": { + "type": "list", + "elements": "dict", + "options": { + "vrf": {"type": "str"}, + "allocate_index": {"type": "int"}, + "bestpath": { + "type": "dict", + "options": { + "always_compare_med": {"type": "bool"}, + "as_path": { + "type": "dict", + "options": { + "ignore": {"type": "bool"}, + "multipath_relax": {"type": "bool"}, + }, + }, + "compare_neighborid": {"type": "bool"}, + "compare_routerid": {"type": "bool"}, + "cost_community_ignore": {"type": "bool"}, + "igp_metric_ignore": {"type": "bool"}, + "med": { + "type": "dict", + "options": { + "confed": {"type": "bool"}, + "missing_as_worst": {"type": "bool"}, + "non_deterministic": {"type": "bool"}, + }, + }, + }, + }, + "cluster_id": {"type": "str"}, + "confederation": { + "type": "dict", + "options": { + "identifier": {"type": "str"}, + "peers": {"type": "list", "elements": "str"}, + }, + }, + "graceful_restart": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "restart_time": {"type": "int"}, + "stalepath_time": {"type": "int"}, + "helper": {"type": "bool"}, + }, + }, + "local_as": {"type": "str"}, + "log_neighbor_changes": {"type": "bool"}, + "maxas_limit": {"type": "int"}, + "neighbors": { + "type": "list", + "elements": "dict", + "options": { + "neighbor_address": { + "type": "str", + "required": True, + }, + "bfd": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "singlehop": {"type": "bool"}, + "multihop": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "interval": { + "type": "dict", + "options": { + "tx_interval": {"type": "int"}, + "min_rx_interval": {"type": "int"}, + "multiplier": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "neighbor_affinity_group": { + "type": "dict", + "options": {"group_id": {"type": "int"}}, + }, + "bmp_activate_server": {"type": "int"}, + "capability": { + "type": "dict", + "options": {"suppress_4_byte_as": {"type": "bool"}}, + }, + "description": {"type": "str"}, + "disable_connected_check": {"type": "bool"}, + "dont_capability_negotiate": {"type": "bool"}, + "dscp": {"type": "str"}, + "dynamic_capability": {"type": "bool"}, + "ebgp_multihop": {"type": "int"}, + "graceful_shutdown": { + "type": "dict", + "options": { + "activate": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + }, + }, + "inherit": { + "type": "dict", + "options": { + "peer": {"type": "str"}, + "peer_session": {"type": "str"}, + }, + }, + "local_as": {"type": "str"}, + "log_neighbor_changes": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "disable": {"type": "bool"}, + }, + }, + "low_memory": { + "type": "dict", + "options": {"exempt": {"type": "bool"}}, + }, + "password": { + "type": "dict", + "no_log": False, + "options": { + "encryption": {"type": "int"}, + "key": {"type": "str", "no_log": True}, + }, + }, + "path_attribute": { + "type": "list", + "elements": "dict", + "options": { + "action": { + "type": "str", + "choices": [ + "discard", + "treat-as-withdraw", + ], + }, + "type": {"type": "int"}, + "range": { + "type": "dict", + "options": { + "start": {"type": "int"}, + "end": {"type": "int"}, + }, + }, + }, + }, + "peer_type": { + "type": "str", + "choices": [ + "fabric-border-leaf", + "fabric-external", + ], + }, + "remote_as": {"type": "str"}, + "remove_private_as": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "replace_as": {"type": "bool"}, + "all": {"type": "bool"}, + }, + }, + "shutdown": {"type": "bool"}, + "timers": { + "type": "dict", + "options": { + "keepalive": {"type": "int"}, + "holdtime": {"type": "int"}, + }, + }, + "transport": { + "type": "dict", + "options": { + "connection_mode": { + "type": "dict", + "options": {"passive": {"type": "bool"}}, + }, + }, + }, + "ttl_security": { + "type": "dict", + "options": {"hops": {"type": "int"}}, + }, + "update_source": {"type": "str"}, + }, + }, + "neighbor_down": { + "type": "dict", + "options": {"fib_accelerate": {"type": "bool"}}, + }, + "reconnect_interval": {"type": "int"}, + "router_id": {"type": "str"}, + "timers": { + "type": "dict", + "options": { + "bestpath_limit": { + "type": "dict", + "options": { + "timeout": {"type": "int"}, + "always": {"type": "bool"}, + }, + }, + "bgp": { + "type": "dict", + "options": { + "keepalive": {"type": "int"}, + "holdtime": {"type": "int"}, + }, + }, + "prefix_peer_timeout": {"type": "int"}, + "prefix_peer_wait": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "deleted", + "purged", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/bgp_neighbor_address_family.py new file mode 100644 index 00000000..4c97d294 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/bgp_neighbor_address_family.py @@ -0,0 +1,377 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_bgp_neighbor_address_family module +""" + + +class Bgp_neighbor_address_familyArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_bgp_neighbor_address_family module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "dict", + "options": { + "as_number": {"type": "str"}, + "neighbors": { + "type": "list", + "elements": "dict", + "options": { + "neighbor_address": {"type": "str", "required": True}, + "address_family": { + "type": "list", + "elements": "dict", + "options": { + "afi": { + "type": "str", + "choices": [ + "ipv4", + "ipv6", + "link-state", + "vpnv4", + "vpnv6", + "l2vpn", + ], + "required": True, + }, + "safi": { + "type": "str", + "choices": [ + "unicast", + "multicast", + "mvpn", + "evpn", + ], + }, + "advertise_map": { + "type": "dict", + "options": { + "route_map": { + "type": "str", + "required": True, + }, + "exist_map": {"type": "str"}, + "non_exist_map": {"type": "str"}, + }, + }, + "advertisement_interval": {"type": "int"}, + "allowas_in": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "max_occurences": {"type": "int"}, + }, + }, + "as_override": {"type": "bool"}, + "capability": { + "type": "dict", + "options": { + "additional_paths": { + "type": "dict", + "options": { + "receive": { + "type": "str", + "choices": [ + "enable", + "disable", + ], + }, + "send": { + "type": "str", + "choices": [ + "enable", + "disable", + ], + }, + }, + }, + }, + }, + "default_originate": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + "disable_peer_as_check": {"type": "bool"}, + "filter_list": { + "type": "dict", + "options": { + "inbound": {"type": "str"}, + "outbound": {"type": "str"}, + }, + }, + "inherit": { + "type": "dict", + "options": { + "template": {"type": "str"}, + "sequence": {"type": "int"}, + }, + }, + "maximum_prefix": { + "type": "dict", + "options": { + "max_prefix_limit": {"type": "int"}, + "generate_warning_threshold": {"type": "int"}, + "restart_interval": {"type": "int"}, + "warning_only": {"type": "bool"}, + }, + }, + "next_hop_self": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "all_routes": {"type": "bool"}, + }, + }, + "next_hop_third_party": {"type": "bool"}, + "prefix_list": { + "type": "dict", + "options": { + "inbound": {"type": "str"}, + "outbound": {"type": "str"}, + }, + }, + "rewrite_evpn_rt_asn": {"type": "bool"}, + "route_map": { + "type": "dict", + "options": { + "inbound": {"type": "str"}, + "outbound": {"type": "str"}, + }, + }, + "route_reflector_client": {"type": "bool"}, + "send_community": { + "type": "dict", + "mutually_exclusive": [ + ["both", "set"], + ["extended", "both"], + ["standard", "both"], + ["standard", "set"], + ], + "options": { + "set": {"type": "bool"}, + "extended": {"type": "bool"}, + "standard": {"type": "bool"}, + "both": {"type": "bool"}, + }, + }, + "soft_reconfiguration_inbound": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "always": {"type": "bool"}, + }, + }, + "soo": {"type": "str"}, + "suppress_inactive": {"type": "bool"}, + "unsuppress_map": {"type": "str"}, + "weight": {"type": "int"}, + }, + }, + }, + }, + "vrfs": { + "type": "list", + "elements": "dict", + "options": { + "vrf": {"type": "str"}, + "neighbors": { + "type": "list", + "elements": "dict", + "options": { + "neighbor_address": { + "type": "str", + "required": True, + }, + "address_family": { + "type": "list", + "elements": "dict", + "options": { + "afi": { + "type": "str", + "choices": [ + "ipv4", + "ipv6", + "link-state", + "vpnv4", + "vpnv6", + "l2vpn", + ], + "required": True, + }, + "safi": { + "type": "str", + "choices": [ + "unicast", + "multicast", + "mvpn", + "evpn", + ], + }, + "advertise_map": { + "type": "dict", + "options": { + "route_map": { + "type": "str", + "required": True, + }, + "exist_map": {"type": "str"}, + "non_exist_map": {"type": "str"}, + }, + }, + "advertisement_interval": {"type": "int"}, + "allowas_in": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "max_occurences": {"type": "int"}, + }, + }, + "as_override": {"type": "bool"}, + "capability": { + "type": "dict", + "options": { + "additional_paths": { + "type": "dict", + "options": { + "receive": { + "type": "str", + "choices": [ + "enable", + "disable", + ], + }, + "send": { + "type": "str", + "choices": [ + "enable", + "disable", + ], + }, + }, + }, + }, + }, + "default_originate": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + "disable_peer_as_check": {"type": "bool"}, + "filter_list": { + "type": "dict", + "options": { + "inbound": {"type": "str"}, + "outbound": {"type": "str"}, + }, + }, + "inherit": { + "type": "dict", + "options": { + "template": {"type": "str"}, + "sequence": {"type": "int"}, + }, + }, + "maximum_prefix": { + "type": "dict", + "options": { + "max_prefix_limit": {"type": "int"}, + "generate_warning_threshold": {"type": "int"}, + "restart_interval": {"type": "int"}, + "warning_only": {"type": "bool"}, + }, + }, + "next_hop_self": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "all_routes": {"type": "bool"}, + }, + }, + "next_hop_third_party": {"type": "bool"}, + "prefix_list": { + "type": "dict", + "options": { + "inbound": {"type": "str"}, + "outbound": {"type": "str"}, + }, + }, + "rewrite_evpn_rt_asn": {"type": "bool"}, + "route_map": { + "type": "dict", + "options": { + "inbound": {"type": "str"}, + "outbound": {"type": "str"}, + }, + }, + "route_reflector_client": {"type": "bool"}, + "send_community": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "extended": {"type": "bool"}, + "standard": {"type": "bool"}, + "both": {"type": "bool"}, + }, + }, + "soft_reconfiguration_inbound": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "always": {"type": "bool"}, + }, + }, + "soo": {"type": "str"}, + "suppress_inactive": {"type": "bool"}, + "unsuppress_map": {"type": "str"}, + "weight": {"type": "int"}, + }, + }, + }, + }, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/facts.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/facts.py new file mode 100644 index 00000000..ae12a8d6 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/facts.py @@ -0,0 +1,25 @@ +# +# -*- 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) +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type +""" +The arg spec for the nxos facts module. +""" + + +class FactsArgs(object): + """The arg spec for the nxos facts module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "gather_subset": dict(default=["min"], type="list", elements="str"), + "gather_network_resources": dict(type="list", elements="str"), + "available_network_resources": {"type": "bool", "default": False}, + } diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/hostname.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/hostname.py new file mode 100644 index 00000000..3a712071 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/hostname.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# 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 + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_hostname module +""" + + +class HostnameArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_hostname module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": {"type": "dict", "options": {"hostname": {"type": "str"}}}, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/hsrp_interfaces.py new file mode 100644 index 00000000..ac302636 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/hsrp_interfaces.py @@ -0,0 +1,60 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# 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 + +""" +The arg spec for the nxos_hsrp_interfaces module +""" + + +class Hsrp_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_hsrp_interfaces module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str"}, + "bfd": {"choices": ["enable", "disable"], "type": "str"}, + }, + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/interfaces.py new file mode 100644 index 00000000..ba8d7923 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/interfaces.py @@ -0,0 +1,67 @@ +# +# -*- 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 nxos_interfaces module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class InterfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_interfaces module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "elements": "dict", + "options": { + "description": {"type": "str"}, + "duplex": {"choices": ["full", "half", "auto"], "type": "str"}, + "enabled": {"type": "bool"}, + "fabric_forwarding_anycast_gateway": {"type": "bool"}, + "ip_forward": {"type": "bool"}, + "mode": {"choices": ["layer2", "layer3"], "type": "str"}, + "mtu": {"type": "str"}, + "name": {"required": True, "type": "str"}, + "speed": {"type": "str"}, + }, + "type": "list", + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + "purged", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/l2_interfaces.py new file mode 100644 index 00000000..046511c7 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/l2_interfaces.py @@ -0,0 +1,73 @@ +# +# -*- 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 nxos_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 nxos_l2_interfaces module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "elements": "dict", + "options": { + "access": { + "options": {"vlan": {"type": "int"}}, + "type": "dict", + }, + "mode": { + "type": "str", + "choices": ["access", "dot1q-tunnel", "trunk", "fex-fabric", "fabricpath"], + }, + "name": {"required": True, "type": "str"}, + "trunk": { + "options": { + "allowed_vlans": {"type": "str"}, + "native_vlan": {"type": "int"}, + }, + "type": "dict", + }, + }, + "type": "list", + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "parsed", + "gathered", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/l3_interfaces.py new file mode 100644 index 00000000..7a5163e0 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/l3_interfaces.py @@ -0,0 +1,83 @@ +# +# -*- 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 nxos_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 nxos_l3_interfaces module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "elements": "dict", + "options": { + "dot1q": {"type": "int"}, + "ipv4": { + "elements": "dict", + "options": { + "address": {"type": "str"}, + "secondary": {"type": "bool"}, + "tag": {"type": "int"}, + }, + "type": "list", + }, + "ipv6": { + "elements": "dict", + "options": { + "address": {"type": "str"}, + "tag": {"type": "int"}, + }, + "type": "list", + }, + "name": {"required": True, "type": "str"}, + "redirects": {"type": "bool"}, + "ipv6_redirects": {"type": "bool"}, + "unreachables": {"type": "bool"}, + "evpn_multisite_tracking": { + "type": "str", + "choices": ["fabric-tracking", "dci-tracking"], + }, + }, + "type": "list", + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/lacp.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/lacp.py new file mode 100644 index 00000000..3c2ac06a --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/lacp.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 nxos_lacp module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class LacpArgs(object): + """The arg spec for the nxos_lacp module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "options": { + "system": { + "options": { + "mac": { + "options": { + "address": {"type": "str"}, + "role": { + "choices": ["primary", "secondary"], + "type": "str", + }, + }, + "type": "dict", + }, + "priority": {"type": "int"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "state": { + "choices": [ + "merged", + "replaced", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000..119432bf --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,76 @@ +# +# -*- 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 nxos_lacp_interfaces module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Lacp_interfacesArgs(object): + """The arg spec for the nxos_lacp_interfaces module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "elements": "dict", + "options": { + "convergence": { + "options": { + "graceful": {"type": "bool"}, + "vpc": {"type": "bool"}, + }, + "type": "dict", + }, + "links": { + "options": { + "max": {"type": "int"}, + "min": {"type": "int"}, + }, + "type": "dict", + }, + "mode": {"choices": ["delay"], "type": "str"}, + "name": {"required": True, "type": "str"}, + "port_priority": {"type": "int"}, + "rate": {"choices": ["fast", "normal"], "type": "str"}, + "suspend_individual": {"type": "bool"}, + }, + "type": "list", + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000..12330b84 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py @@ -0,0 +1,70 @@ +# -*- 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 nxos_lag_interfaces module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Lag_interfacesArgs(object): + """The arg spec for the nxos_lag_interfaces module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "elements": "dict", + "options": { + "members": { + "elements": "dict", + "options": { + "member": {"type": "str"}, + "mode": { + "type": "str", + "choices": ["active", "on", "passive"], + }, + "force": {"type": "bool"}, + }, + "type": "list", + }, + "name": {"required": True, "type": "str"}, + }, + "type": "list", + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "parsed", + "gathered", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/lldp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/lldp_global.py new file mode 100644 index 00000000..8fad5eba --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/lldp_global.py @@ -0,0 +1,88 @@ +# +# -*- 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 nxos_lldp_global module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Lldp_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_lldp_global module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "options": { + "holdtime": {"type": "int"}, + "port_id": {"choices": [0, 1], "type": "int"}, + "reinit": {"type": "int"}, + "timer": {"type": "int"}, + "tlv_select": { + "options": { + "dcbxp": {"type": "bool"}, + "management_address": { + "options": { + "v4": {"type": "bool"}, + "v6": {"type": "bool"}, + }, + "type": "dict", + }, + "port": { + "options": { + "description": {"type": "bool"}, + "vlan": {"type": "bool"}, + }, + "type": "dict", + }, + "power_management": {"type": "bool"}, + "system": { + "options": { + "capabilities": {"type": "bool"}, + "description": {"type": "bool"}, + "name": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "state": { + "choices": [ + "merged", + "replaced", + "deleted", + "gathered", + "parsed", + "rendered", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..0552901e --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,67 @@ +# +# -*- 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 nxos_lldp_interfaces module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Lldp_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_lldp_interfaces module""" + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "name": {"required": True, "type": "str"}, + "receive": {"type": "bool"}, + "tlv_set": { + "options": { + "management_address": {"type": "str"}, + "vlan": {"type": "int"}, + }, + "type": "dict", + }, + "transmit": {"type": "bool"}, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "deleted", + "gathered", + "merged", + "overridden", + "rendered", + "replaced", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/logging_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/logging_global.py new file mode 100644 index 00000000..58a9052f --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/logging_global.py @@ -0,0 +1,276 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_logging_global module +""" + + +class Logging_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_logging_global module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "dict", + "options": { + "console": { + "type": "dict", + "options": { + "state": { + "type": "str", + "choices": ["enabled", "disabled"], + }, + "severity": { + "type": "str", + "choices": [ + "emergency", + "alert", + "critical", + "error", + "warning", + "notification", + "informational", + "debugging", + ], + }, + }, + }, + "event": { + "type": "dict", + "options": { + "link_status": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "default": {"type": "bool"}, + }, + }, + "trunk_status": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "default": {"type": "bool"}, + }, + }, + }, + }, + "history": { + "type": "dict", + "options": { + "severity": { + "type": "str", + "choices": [ + "emergency", + "alert", + "critical", + "error", + "warning", + "notification", + "informational", + "debugging", + ], + }, + "size": {"type": "int"}, + }, + }, + "ip": { + "type": "dict", + "options": { + "access_list": { + "type": "dict", + "options": { + "cache": { + "type": "dict", + "options": { + "entries": {"type": "int"}, + "interval": {"type": "int"}, + "threshold": {"type": "int"}, + }, + }, + "detailed": {"type": "bool"}, + "include": { + "type": "dict", + "options": {"sgt": {"type": "bool"}}, + }, + }, + }, + }, + }, + "facilities": { + "type": "list", + "elements": "dict", + "options": { + "facility": {"type": "str"}, + "severity": { + "type": "str", + "choices": [ + "emergency", + "alert", + "critical", + "error", + "warning", + "notification", + "informational", + "debugging", + ], + }, + }, + }, + "logfile": { + "type": "dict", + "options": { + "state": { + "type": "str", + "choices": ["enabled", "disabled"], + }, + "name": {"type": "str"}, + "severity": { + "type": "str", + "choices": [ + "emergency", + "alert", + "critical", + "error", + "warning", + "notification", + "informational", + "debugging", + ], + }, + "persistent_threshold": {"type": "int"}, + "size": {"type": "int"}, + }, + }, + "module": { + "type": "dict", + "options": { + "state": { + "type": "str", + "choices": ["enabled", "disabled"], + }, + "severity": { + "type": "str", + "choices": [ + "emergency", + "alert", + "critical", + "error", + "warning", + "notification", + "informational", + "debugging", + ], + }, + }, + }, + "monitor": { + "type": "dict", + "options": { + "state": { + "type": "str", + "choices": ["enabled", "disabled"], + }, + "severity": { + "type": "str", + "choices": [ + "emergency", + "alert", + "critical", + "error", + "warning", + "notification", + "informational", + "debugging", + ], + }, + }, + }, + "origin_id": { + "type": "dict", + "options": { + "hostname": {"type": "bool"}, + "ip": {"type": "str"}, + "string": {"type": "str"}, + }, + }, + "rate_limit": { + "type": "str", + "choices": ["enabled", "disabled"], + }, + "rfc_strict": {"type": "bool"}, + "hosts": { + "type": "list", + "elements": "dict", + "options": { + "host": {"type": "str"}, + "severity": { + "type": "str", + "choices": [ + "emergency", + "alert", + "critical", + "error", + "warning", + "notification", + "informational", + "debugging", + ], + }, + "facility": {"type": "str"}, + "port": {"type": "int"}, + "secure": { + "type": "dict", + "options": { + "trustpoint": { + "type": "dict", + "options": {"client_identity": {"type": "str"}}, + }, + }, + }, + "use_vrf": {"type": "str"}, + }, + }, + "source_interface": {"type": "str"}, + "timestamp": { + "type": "str", + "choices": ["microseconds", "milliseconds", "seconds"], + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/ntp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/ntp_global.py new file mode 100644 index 00000000..a680e58d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/ntp_global.py @@ -0,0 +1,139 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_ntp_global module +""" + + +class Ntp_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_ntp_global module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "dict", + "options": { + "access_group": { + "type": "dict", + "options": { + "match_all": {"type": "bool"}, + "peer": { + "type": "list", + "elements": "dict", + "options": {"access_list": {"type": "str"}}, + }, + "query_only": { + "type": "list", + "elements": "dict", + "options": {"access_list": {"type": "str"}}, + }, + "serve": { + "type": "list", + "elements": "dict", + "options": {"access_list": {"type": "str"}}, + }, + "serve_only": { + "type": "list", + "elements": "dict", + "options": {"access_list": {"type": "str"}}, + }, + }, + }, + "allow": { + "type": "dict", + "options": { + "control": { + "type": "dict", + "options": {"rate_limit": {"type": "int"}}, + }, + "private": {"type": "bool"}, + }, + }, + "authenticate": {"type": "bool"}, + "authentication_keys": { + "type": "list", + "elements": "dict", + "no_log": False, + "options": { + "id": {"type": "int"}, + "key": {"type": "str", "no_log": True}, + "encryption": {"type": "int"}, + }, + }, + "logging": {"type": "bool"}, + "master": { + "type": "dict", + "options": {"stratum": {"type": "int"}}, + }, + "passive": {"type": "bool"}, + "peers": { + "type": "list", + "elements": "dict", + "options": { + "peer": {"type": "str"}, + "key_id": {"type": "int"}, + "maxpoll": {"type": "int"}, + "minpoll": {"type": "int"}, + "prefer": {"type": "bool"}, + "vrf": {"type": "str", "aliases": ["use_vrf"]}, + }, + }, + "servers": { + "type": "list", + "elements": "dict", + "options": { + "server": {"type": "str"}, + "key_id": {"type": "int"}, + "maxpoll": {"type": "int"}, + "minpoll": {"type": "int"}, + "prefer": {"type": "bool"}, + "vrf": {"type": "str", "aliases": ["use_vrf"]}, + }, + }, + "source": {"type": "str"}, + "source_interface": {"type": "str"}, + "trusted_keys": { + "type": "list", + "elements": "dict", + "no_log": False, + "options": {"key_id": {"type": "int"}}, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/ospf_interfaces.py new file mode 100644 index 00000000..30d65035 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/ospf_interfaces.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# 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 + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_ospf_interfaces module +""" + + +class Ospf_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_ospf_interfaces module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str", "required": True}, + "address_family": { + "type": "list", + "elements": "dict", + "mutually_exclusive": [["passive_interface", "default_passive_interface"]], + "options": { + "afi": { + "type": "str", + "choices": ["ipv4", "ipv6"], + "required": True, + }, + "processes": { + "type": "list", + "elements": "dict", + "options": { + "process_id": { + "type": "str", + "required": True, + }, + "area": { + "type": "dict", + "options": { + "area_id": { + "type": "str", + "required": True, + }, + "secondaries": {"type": "bool"}, + }, + }, + "multi_areas": { + "type": "list", + "elements": "str", + }, + }, + }, + "multi_areas": {"type": "list", "elements": "str"}, + "authentication": { + "type": "dict", + "options": { + "key_chain": {"type": "str", "no_log": False}, + "message_digest": {"type": "bool"}, + "enable": {"type": "bool"}, + "null_auth": {"type": "bool"}, + }, + }, + "authentication_key": { + "type": "dict", + "no_log": False, + "options": { + "encryption": {"type": "int"}, + "key": { + "type": "str", + "required": True, + "no_log": True, + }, + }, + }, + "message_digest_key": { + "type": "dict", + "no_log": False, + "options": { + "key_id": {"type": "int", "required": True}, + "encryption": {"type": "int"}, + "key": { + "type": "str", + "required": True, + "no_log": True, + }, + }, + }, + "cost": {"type": "int"}, + "dead_interval": {"type": "int"}, + "hello_interval": {"type": "int"}, + "instance": {"type": "int"}, + "mtu_ignore": {"type": "bool"}, + "network": { + "type": "str", + "choices": ["broadcast", "point-to-point"], + }, + "default_passive_interface": {"type": "bool"}, + "passive_interface": {"type": "bool"}, + "priority": {"type": "int"}, + "retransmit_interval": {"type": "int"}, + "shutdown": {"type": "bool"}, + "transmit_delay": {"type": "int"}, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "parsed", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/ospfv2.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/ospfv2.py new file mode 100644 index 00000000..2e5e62f1 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/ospfv2.py @@ -0,0 +1,622 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2020 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. +# +############################################# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The arg spec for the nxos_ospfv2 module +""" + + +class Ospfv2Args(object): # pylint: disable=R0903 + """The arg spec for the nxos_ospfv2 module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "options": { + "processes": { + "elements": "dict", + "options": { + "areas": { + "required_one_of": [ + [ + "authentication", + "default_cost", + "filter_list", + "nssa", + "ranges", + "stub", + ], + ], + "elements": "dict", + "options": { + "area_id": {"type": "str", "required": True}, + "authentication": { + "options": { + "set": {"type": "bool"}, + "message_digest": {"type": "bool"}, + }, + "type": "dict", + }, + "default_cost": {"type": "int"}, + "filter_list": { + "options": { + "direction": { + "choices": ["in", "out"], + "type": "str", + "required": True, + }, + "route_map": { + "type": "str", + "required": True, + }, + }, + "type": "list", + "elements": "dict", + }, + "nssa": { + "options": { + "default_information_originate": {"type": "bool"}, + "no_redistribution": {"type": "bool"}, + "no_summary": {"type": "bool"}, + "set": {"type": "bool"}, + "translate": { + "options": { + "type7": { + "mutually_exclusive": [["always", "never"]], + "options": { + "always": {"type": "bool"}, + "never": {"type": "bool"}, + "supress_fa": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "ranges": { + "elements": "dict", + "options": { + "cost": {"type": "int"}, + "not_advertise": {"type": "bool"}, + "prefix": { + "type": "str", + "required": True, + }, + }, + "type": "list", + }, + "stub": { + "options": { + "no_summary": {"type": "bool"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "list", + }, + "auto_cost": { + "options": { + "reference_bandwidth": { + "type": "int", + "required": True, + }, + "unit": { + "choices": ["Gbps", "Mbps"], + "type": "str", + "required": True, + }, + }, + "type": "dict", + }, + "bfd": {"type": "bool"}, + "default_information": { + "options": { + "originate": { + "options": { + "always": {"type": "bool"}, + "route_map": {"type": "str"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "default_metric": {"type": "int"}, + "distance": {"type": "int"}, + "flush_routes": {"type": "bool"}, + "graceful_restart": { + "options": { + "grace_period": {"type": "int"}, + "helper_disable": {"type": "bool"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + "isolate": {"type": "bool"}, + "log_adjacency_changes": { + "options": { + "detail": {"type": "bool"}, + "log": {"type": "bool"}, + }, + "type": "dict", + }, + "max_lsa": { + "options": { + "ignore_count": {"type": "int"}, + "ignore_time": {"type": "int"}, + "max_non_self_generated_lsa": { + "type": "int", + "required": True, + }, + "reset_time": {"type": "int"}, + "threshold": {"type": "int"}, + "warning_only": {"type": "bool"}, + }, + "type": "dict", + }, + "max_metric": { + "options": { + "router_lsa": { + "options": { + "external_lsa": { + "options": { + "max_metric_value": {"type": "int"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + "include_stub": {"type": "bool"}, + "on_startup": { + "options": { + "set": {"type": "bool"}, + "wait_for_bgp_asn": {"type": "int"}, + "wait_period": {"type": "int"}, + }, + "type": "dict", + }, + "set": {"type": "bool"}, + "summary_lsa": { + "options": { + "max_metric_value": {"type": "int"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "maximum_paths": {"type": "int"}, + "mpls": { + "options": { + "traffic_eng": { + "options": { + "areas": { + "type": "list", + "elements": "dict", + "options": {"area_id": {"type": "str"}}, + }, + "multicast_intact": {"type": "bool"}, + "router_id": {"type": "str"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "name_lookup": {"type": "bool"}, + "passive_interface": { + "options": {"default": {"type": "bool"}}, + "type": "dict", + }, + "process_id": {"required": True, "type": "str"}, + "redistribute": { + "elements": "dict", + "options": { + "id": {"type": "str"}, + "protocol": { + "choices": [ + "bgp", + "direct", + "eigrp", + "isis", + "lisp", + "ospf", + "rip", + "static", + ], + "required": True, + "type": "str", + }, + "route_map": {"type": "str", "required": True}, + }, + "type": "list", + }, + "rfc1583compatibility": {"type": "bool"}, + "router_id": {"type": "str"}, + "shutdown": {"type": "bool"}, + "summary_address": { + "elements": "dict", + "mutually_exclusive": [["not_advertise", "tag"]], + "options": { + "not_advertise": {"type": "bool"}, + "prefix": {"type": "str", "required": True}, + "tag": {"type": "int"}, + }, + "type": "list", + }, + "table_map": { + "options": { + "filter": {"type": "bool"}, + "name": {"type": "str", "required": True}, + }, + "type": "dict", + }, + "timers": { + "options": { + "lsa_arrival": {"type": "int"}, + "lsa_group_pacing": {"type": "int"}, + "throttle": { + "options": { + "lsa": { + "options": { + "hold_interval": {"type": "int"}, + "max_interval": {"type": "int"}, + "start_interval": {"type": "int"}, + }, + "type": "dict", + }, + "spf": { + "options": { + "initial_spf_delay": {"type": "int"}, + "max_wait_time": {"type": "int"}, + "min_hold_time": {"type": "int"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "vrfs": { + "elements": "dict", + "options": { + "areas": { + "required_one_of": [ + [ + "authentication", + "default_cost", + "filter_list", + "nssa", + "ranges", + "stub", + ], + ], + "elements": "dict", + "options": { + "area_id": { + "type": "str", + "required": True, + }, + "authentication": { + "options": { + "set": {"type": "bool"}, + "message_digest": {"type": "bool"}, + }, + "type": "dict", + }, + "default_cost": {"type": "int"}, + "filter_list": { + "options": { + "direction": { + "choices": ["in", "out"], + "type": "str", + "required": True, + }, + "route_map": { + "type": "str", + "required": True, + }, + }, + "type": "list", + "elements": "dict", + }, + "nssa": { + "options": { + "default_information_originate": {"type": "bool"}, + "no_redistribution": {"type": "bool"}, + "no_summary": {"type": "bool"}, + "set": {"type": "bool"}, + "translate": { + "options": { + "type7": { + "mutually_exclusive": [ + [ + "always", + "never", + ], + ], + "options": { + "always": {"type": "bool"}, + "never": {"type": "bool"}, + "supress_fa": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "ranges": { + "elements": "dict", + "options": { + "cost": {"type": "int"}, + "not_advertise": {"type": "bool"}, + "prefix": { + "type": "str", + "required": True, + }, + }, + "type": "list", + }, + "stub": { + "options": { + "no_summary": {"type": "bool"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "list", + }, + "auto_cost": { + "options": { + "reference_bandwidth": { + "type": "int", + "required": True, + }, + "unit": { + "choices": ["Gbps", "Mbps"], + "type": "str", + "required": True, + }, + }, + "type": "dict", + }, + "bfd": {"type": "bool"}, + "capability": { + "type": "dict", + "options": { + "vrf_lite": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "evpn": {"type": "bool"}, + }, + }, + }, + }, + "default_information": { + "options": { + "originate": { + "options": { + "always": {"type": "bool"}, + "route_map": {"type": "str"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "default_metric": {"type": "int"}, + "distance": {"type": "int"}, + "down_bit_ignore": {"type": "bool"}, + "graceful_restart": { + "options": { + "grace_period": {"type": "int"}, + "helper_disable": {"type": "bool"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + "log_adjacency_changes": { + "options": { + "detail": {"type": "bool"}, + "log": {"type": "bool"}, + }, + "type": "dict", + }, + "max_lsa": { + "options": { + "ignore_count": {"type": "int"}, + "ignore_time": {"type": "int"}, + "max_non_self_generated_lsa": { + "type": "int", + "required": True, + }, + "reset_time": {"type": "int"}, + "threshold": {"type": "int"}, + "warning_only": {"type": "bool"}, + }, + "type": "dict", + }, + "max_metric": { + "options": { + "router_lsa": { + "options": { + "external_lsa": { + "options": { + "max_metric_value": {"type": "int"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + "include_stub": {"type": "bool"}, + "on_startup": { + "options": { + "set": {"type": "bool"}, + "wait_for_bgp_asn": {"type": "int"}, + "wait_period": {"type": "int"}, + }, + "type": "dict", + }, + "set": {"type": "bool"}, + "summary_lsa": { + "options": { + "max_metric_value": {"type": "int"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "maximum_paths": {"type": "int"}, + "name_lookup": {"type": "bool"}, + "passive_interface": { + "options": {"default": {"type": "bool"}}, + "type": "dict", + }, + "redistribute": { + "elements": "dict", + "options": { + "id": {"type": "str"}, + "protocol": { + "choices": [ + "bgp", + "direct", + "eigrp", + "isis", + "lisp", + "ospf", + "rip", + "static", + ], + "required": True, + "type": "str", + }, + "route_map": { + "type": "str", + "required": True, + }, + }, + "type": "list", + }, + "rfc1583compatibility": {"type": "bool"}, + "router_id": {"type": "str"}, + "shutdown": {"type": "bool"}, + "summary_address": { + "elements": "dict", + "options": { + "not_advertise": {"type": "bool"}, + "prefix": { + "type": "str", + "required": True, + }, + "tag": {"type": "int"}, + }, + "type": "list", + }, + "table_map": { + "options": { + "filter": {"type": "bool"}, + "name": { + "type": "str", + "required": True, + }, + }, + "type": "dict", + }, + "timers": { + "options": { + "lsa_arrival": {"type": "int"}, + "lsa_group_pacing": {"type": "int"}, + "throttle": { + "options": { + "lsa": { + "options": { + "hold_interval": {"type": "int"}, + "max_interval": {"type": "int"}, + "start_interval": {"type": "int"}, + }, + "type": "dict", + }, + "spf": { + "options": { + "initial_spf_delay": {"type": "int"}, + "max_wait_time": {"type": "int"}, + "min_hold_time": {"type": "int"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "vrf": {"required": True, "type": "str"}, + }, + "type": "list", + }, + }, + "type": "list", + }, + }, + "type": "dict", + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/ospfv3.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/ospfv3.py new file mode 100644 index 00000000..601bb618 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/ospfv3.py @@ -0,0 +1,488 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# 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 + +############################################# +# 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 nxos_ospfv3 module +""" + + +class Ospfv3Args(object): # pylint: disable=R0903 + """The arg spec for the nxos_ospfv3 module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "dict", + "options": { + "processes": { + "type": "list", + "elements": "dict", + "options": { + "address_family": { + "type": "dict", + "options": { + "afi": {"type": "str", "choices": ["ipv6"]}, + "safi": { + "type": "str", + "choices": ["unicast"], + }, + "areas": { + "type": "list", + "elements": "dict", + "options": { + "area_id": { + "type": "str", + "required": True, + }, + "default_cost": {"type": "int"}, + "filter_list": { + "type": "list", + "elements": "dict", + "options": { + "route_map": { + "type": "str", + "required": True, + }, + "direction": { + "type": "str", + "choices": ["in", "out"], + "required": True, + }, + }, + }, + "ranges": { + "type": "list", + "elements": "dict", + "options": { + "prefix": { + "type": "str", + "required": True, + }, + "cost": {"type": "int"}, + "not_advertise": {"type": "bool"}, + }, + }, + }, + }, + "default_information": { + "type": "dict", + "options": { + "originate": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "always": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + }, + }, + "distance": {"type": "int"}, + "maximum_paths": {"type": "int"}, + "redistribute": { + "type": "list", + "elements": "dict", + "options": { + "protocol": { + "type": "str", + "choices": [ + "bgp", + "direct", + "eigrp", + "isis", + "lisp", + "ospfv3", + "rip", + "static", + ], + "required": True, + }, + "id": {"type": "str"}, + "route_map": { + "type": "str", + "required": True, + }, + }, + }, + "summary_address": { + "type": "list", + "elements": "dict", + "options": { + "prefix": { + "type": "str", + "required": True, + }, + "not_advertise": {"type": "bool"}, + "tag": {"type": "int"}, + }, + }, + "table_map": { + "type": "dict", + "options": { + "name": { + "type": "str", + "required": True, + }, + "filter": {"type": "bool"}, + }, + }, + "timers": { + "type": "dict", + "options": { + "throttle": { + "type": "dict", + "options": { + "spf": { + "type": "dict", + "options": { + "initial_spf_delay": {"type": "int"}, + "min_hold_time": {"type": "int"}, + "max_wait_time": {"type": "int"}, + }, + }, + }, + }, + }, + }, + }, + }, + "areas": { + "type": "list", + "elements": "dict", + "options": { + "area_id": {"type": "str", "required": True}, + "nssa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "default_information_originate": {"type": "bool"}, + "no_redistribution": {"type": "bool"}, + "no_summary": {"type": "bool"}, + "route_map": {"type": "str"}, + "translate": { + "type": "dict", + "options": { + "type7": { + "type": "dict", + "options": { + "always": {"type": "bool"}, + "never": {"type": "bool"}, + "supress_fa": {"type": "bool"}, + }, + }, + }, + }, + }, + }, + "stub": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "no_summary": {"type": "bool"}, + }, + }, + }, + }, + "auto_cost": { + "type": "dict", + "options": { + "reference_bandwidth": { + "type": "int", + "required": True, + }, + "unit": { + "type": "str", + "required": True, + "choices": ["Gbps", "Mbps"], + }, + }, + }, + "flush_routes": {"type": "bool"}, + "graceful_restart": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "grace_period": {"type": "int"}, + "helper_disable": {"type": "bool"}, + "planned_only": {"type": "bool"}, + }, + }, + "isolate": {"type": "bool"}, + "log_adjacency_changes": { + "type": "dict", + "options": { + "log": {"type": "bool"}, + "detail": {"type": "bool"}, + }, + }, + "max_lsa": { + "type": "dict", + "options": { + "max_non_self_generated_lsa": { + "type": "int", + "required": True, + }, + "threshold": {"type": "int"}, + "ignore_count": {"type": "int"}, + "ignore_time": {"type": "int"}, + "reset_time": {"type": "int"}, + "warning_only": {"type": "bool"}, + }, + }, + "max_metric": { + "type": "dict", + "options": { + "router_lsa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "external_lsa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "max_metric_value": {"type": "int"}, + }, + }, + "stub_prefix_lsa": {"type": "bool"}, + "on_startup": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "wait_period": {"type": "int"}, + "wait_for_bgp_asn": {"type": "int"}, + }, + }, + "inter_area_prefix_lsa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "max_metric_value": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "name_lookup": {"type": "bool"}, + "passive_interface": { + "type": "dict", + "options": {"default": {"type": "bool"}}, + }, + "process_id": {"type": "str", "required": True}, + "router_id": {"type": "str"}, + "shutdown": {"type": "bool"}, + "timers": { + "type": "dict", + "options": { + "lsa_arrival": {"type": "int"}, + "lsa_group_pacing": {"type": "int"}, + "throttle": { + "type": "dict", + "options": { + "lsa": { + "type": "dict", + "options": { + "start_interval": {"type": "int"}, + "hold_interval": {"type": "int"}, + "max_interval": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "vrfs": { + "type": "list", + "elements": "dict", + "options": { + "areas": { + "type": "list", + "elements": "dict", + "options": { + "area_id": { + "type": "str", + "required": True, + }, + "nssa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "default_information_originate": {"type": "bool"}, + "no_redistribution": {"type": "bool"}, + "no_summary": {"type": "bool"}, + "route_map": {"type": "str"}, + "translate": { + "type": "dict", + "options": { + "type7": { + "type": "dict", + "options": { + "always": {"type": "bool"}, + "never": {"type": "bool"}, + "supress_fa": {"type": "bool"}, + }, + }, + }, + }, + }, + }, + "stub": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "no_summary": {"type": "bool"}, + }, + }, + }, + }, + "auto_cost": { + "type": "dict", + "options": { + "reference_bandwidth": { + "type": "int", + "required": True, + }, + "unit": { + "type": "str", + "required": True, + "choices": ["Gbps", "Mbps"], + }, + }, + }, + "graceful_restart": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "grace_period": {"type": "int"}, + "helper_disable": {"type": "bool"}, + "planned_only": {"type": "bool"}, + }, + }, + "log_adjacency_changes": { + "type": "dict", + "options": { + "log": {"type": "bool"}, + "detail": {"type": "bool"}, + }, + }, + "max_lsa": { + "type": "dict", + "options": { + "max_non_self_generated_lsa": { + "type": "int", + "required": True, + }, + "threshold": {"type": "int"}, + "ignore_count": {"type": "int"}, + "ignore_time": {"type": "int"}, + "reset_time": {"type": "int"}, + "warning_only": {"type": "bool"}, + }, + }, + "max_metric": { + "type": "dict", + "options": { + "router_lsa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "external_lsa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "max_metric_value": {"type": "int"}, + }, + }, + "stub_prefix_lsa": {"type": "bool"}, + "on_startup": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "wait_period": {"type": "int"}, + "wait_for_bgp_asn": {"type": "int"}, + }, + }, + "inter_area_prefix_lsa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "max_metric_value": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "name_lookup": {"type": "bool"}, + "passive_interface": { + "type": "dict", + "options": {"default": {"type": "bool"}}, + }, + "router_id": {"type": "str"}, + "shutdown": {"type": "bool"}, + "timers": { + "type": "dict", + "options": { + "lsa_arrival": {"type": "int"}, + "lsa_group_pacing": {"type": "int"}, + "throttle": { + "type": "dict", + "options": { + "lsa": { + "type": "dict", + "options": { + "start_interval": {"type": "int"}, + "hold_interval": {"type": "int"}, + "max_interval": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "vrf": {"type": "str", "required": True}, + }, + }, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "parsed", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/prefix_lists.py new file mode 100644 index 00000000..c29e0375 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/prefix_lists.py @@ -0,0 +1,80 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_prefix_lists module +""" + + +class Prefix_listsArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_prefix_lists module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "list", + "elements": "dict", + "options": { + "afi": {"type": "str", "choices": ["ipv4", "ipv6"]}, + "prefix_lists": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str"}, + "description": {"type": "str"}, + "entries": { + "type": "list", + "elements": "dict", + "options": { + "sequence": {"type": "int"}, + "action": { + "type": "str", + "choices": ["permit", "deny"], + }, + "prefix": {"type": "str"}, + "eq": {"type": "int"}, + "ge": {"type": "int"}, + "le": {"type": "int"}, + "mask": {"type": "str"}, + }, + }, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/route_maps.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/route_maps.py new file mode 100644 index 00000000..25e40d6b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/route_maps.py @@ -0,0 +1,412 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_route_maps module +""" + + +class Route_mapsArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_route_maps module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "list", + "elements": "dict", + "options": { + "route_map": {"type": "str"}, + "entries": { + "type": "list", + "elements": "dict", + "options": { + "sequence": {"type": "int"}, + "action": { + "type": "str", + "choices": ["deny", "permit"], + }, + "continue_sequence": {"type": "int"}, + "description": {"type": "str"}, + "match": { + "type": "dict", + "options": { + "as_number": { + "type": "dict", + "options": { + "asn": { + "type": "list", + "elements": "str", + }, + "as_path_list": { + "type": "list", + "elements": "str", + }, + }, + }, + "as_path": {"type": "list", "elements": "str"}, + "community": { + "type": "dict", + "options": { + "community_list": { + "type": "list", + "elements": "str", + }, + "exact_match": {"type": "bool"}, + }, + }, + "evpn": { + "type": "dict", + "options": { + "route_types": { + "type": "list", + "elements": "str", + }, + }, + }, + "extcommunity": { + "type": "dict", + "options": { + "extcommunity_list": { + "type": "list", + "elements": "str", + }, + "exact_match": {"type": "bool"}, + }, + }, + "interfaces": { + "type": "list", + "elements": "str", + }, + "ip": { + "type": "dict", + "options": { + "address": { + "type": "dict", + "options": { + "access_list": {"type": "str"}, + "prefix_lists": { + "type": "list", + "elements": "str", + }, + }, + }, + "multicast": { + "type": "dict", + "options": { + "source": {"type": "str"}, + "group": { + "type": "dict", + "options": {"prefix": {"type": "str"}}, + }, + "group_range": { + "type": "dict", + "options": { + "first": {"type": "str"}, + "last": {"type": "str"}, + }, + }, + "rp": { + "type": "dict", + "options": { + "prefix": {"type": "str"}, + "rp_type": { + "type": "str", + "choices": [ + "ASM", + "Bidir", + ], + }, + }, + }, + }, + }, + "next_hop": { + "type": "dict", + "options": { + "prefix_lists": { + "type": "list", + "elements": "str", + }, + }, + }, + "route_source": { + "type": "dict", + "options": { + "prefix_lists": { + "type": "list", + "elements": "str", + }, + }, + }, + }, + }, + "ipv6": { + "type": "dict", + "options": { + "address": { + "type": "dict", + "options": { + "access_list": {"type": "str"}, + "prefix_lists": { + "type": "list", + "elements": "str", + }, + }, + }, + "multicast": { + "type": "dict", + "options": { + "source": {"type": "str"}, + "group": { + "type": "dict", + "options": {"prefix": {"type": "str"}}, + }, + "group_range": { + "type": "dict", + "options": { + "first": {"type": "str"}, + "last": {"type": "str"}, + }, + }, + "rp": { + "type": "dict", + "options": { + "prefix": {"type": "str"}, + "rp_type": { + "type": "str", + "choices": [ + "ASM", + "Bidir", + ], + }, + }, + }, + }, + }, + "next_hop": { + "type": "dict", + "options": { + "prefix_lists": { + "type": "list", + "elements": "str", + }, + }, + }, + "route_source": { + "type": "dict", + "options": { + "prefix_lists": { + "type": "list", + "elements": "str", + }, + }, + }, + }, + }, + "mac_list": { + "type": "list", + "elements": "str", + }, + "metric": {"type": "list", "elements": "int"}, + "ospf_area": { + "type": "list", + "elements": "int", + }, + "route_types": { + "type": "list", + "elements": "str", + "choices": [ + "external", + "inter-area", + "internal", + "intra-area", + "level-1", + "level-2", + "local", + "nssa-external", + "type-1", + "type-2", + ], + }, + "source_protocol": { + "type": "list", + "elements": "str", + }, + "tags": {"type": "list", "elements": "int"}, + }, + }, + "set": { + "type": "dict", + "options": { + "as_path": { + "type": "dict", + "options": { + "prepend": { + "type": "dict", + "options": { + "as_number": { + "type": "list", + "elements": "str", + }, + "last_as": {"type": "int"}, + }, + }, + "tag": {"type": "bool"}, + }, + }, + "comm_list": {"type": "str"}, + "community": { + "type": "dict", + "options": { + "additive": {"type": "bool"}, + "graceful_shutdown": {"type": "bool"}, + "internet": {"type": "bool"}, + "local_as": {"type": "bool"}, + "no_advertise": {"type": "bool"}, + "no_export": {"type": "bool"}, + "number": { + "type": "list", + "elements": "str", + }, + }, + }, + "dampening": { + "type": "dict", + "options": { + "half_life": {"type": "int"}, + "start_reuse_route": {"type": "int"}, + "start_suppress_route": {"type": "int"}, + "max_suppress_time": {"type": "int"}, + }, + }, + "distance": { + "type": "dict", + "options": { + "igp_ebgp_routes": {"type": "int"}, + "internal_routes": {"type": "int"}, + "local_routes": {"type": "int"}, + }, + }, + "evpn": { + "type": "dict", + "options": { + "gateway_ip": { + "type": "dict", + "mutually_exclusive": [["ip", "use_nexthop"]], + "options": { + "ip": {"type": "str"}, + "use_nexthop": {"type": "bool"}, + }, + }, + }, + }, + "extcomm_list": {"type": "str"}, + "forwarding_address": {"type": "bool"}, + "null_interface": {"type": "str"}, + "ip": { + "type": "dict", + "options": { + "address": { + "type": "dict", + "options": {"prefix_list": {"type": "str"}}, + }, + "precedence": {"type": "str"}, + }, + }, + "ipv6": { + "type": "dict", + "options": { + "address": { + "type": "dict", + "options": {"prefix_list": {"type": "str"}}, + }, + "precedence": {"type": "str"}, + }, + }, + "label_index": {"type": "int"}, + "level": { + "type": "str", + "choices": [ + "level-1", + "level-1-2", + "level-2", + ], + }, + "local_preference": {"type": "int"}, + "metric": { + "type": "dict", + "options": { + "bandwidth": {"type": "int"}, + "igrp_delay_metric": {"type": "int"}, + "igrp_reliability_metric": {"type": "int"}, + "igrp_effective_bandwidth_metric": {"type": "int"}, + "igrp_mtu": {"type": "int"}, + }, + }, + "metric_type": { + "type": "str", + "choices": [ + "external", + "internal", + "type-1", + "type-2", + ], + }, + "nssa_only": {"type": "bool"}, + "origin": { + "type": "str", + "choices": ["egp", "igp", "incomplete"], + }, + "path_selection": { + "type": "str", + "choices": [ + "all", + "backup", + "best2", + "multipaths", + ], + }, + "tag": {"type": "int"}, + "weight": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/snmp_server.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/snmp_server.py new file mode 100644 index 00000000..a6b3d420 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/snmp_server.py @@ -0,0 +1,411 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the nxos_snmp_server module +""" + + +class Snmp_serverArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_snmp_server module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "type": "dict", + "options": { + "aaa_user": { + "type": "dict", + "options": {"cache_timeout": {"type": "int"}}, + }, + "communities": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str", "aliases": ["community"]}, + "group": {"type": "str"}, + "ro": {"type": "bool"}, + "rw": {"type": "bool"}, + "use_ipv4acl": {"type": "str"}, + "use_ipv6acl": {"type": "str"}, + }, + }, + "contact": {"type": "str"}, + "context": { + "type": "dict", + "options": { + "name": {"type": "str"}, + "instance": {"type": "str"}, + "topology": {"type": "str"}, + "vrf": {"type": "str"}, + }, + }, + "counter": { + "type": "dict", + "options": { + "cache": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "timeout": {"type": "int"}, + }, + }, + }, + }, + "drop": { + "type": "dict", + "options": { + "unknown_engine_id": {"type": "bool"}, + "unknown_user": {"type": "bool"}, + }, + }, + "traps": { + "type": "dict", + "options": { + "aaa": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "server_state_change": {"type": "bool"}, + }, + }, + "bgp": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + }, + }, + "bridge": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "newroot": {"type": "bool"}, + "topologychange": {"type": "bool"}, + }, + }, + "callhome": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "event_notify": {"type": "bool"}, + "smtp_send_fail": {"type": "bool"}, + }, + }, + "cfs": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "merge_failure": {"type": "bool"}, + "state_change_notif": {"type": "bool"}, + }, + }, + "config": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "ccmCLIRunningConfigChanged": {"type": "bool"}, + }, + }, + "entity": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "cefcMIBEnableStatusNotification": {"type": "bool"}, + "entity_fan_status_change": {"type": "bool"}, + "entity_mib_change": {"type": "bool"}, + "entity_module_inserted": {"type": "bool"}, + "entity_module_removed": {"type": "bool"}, + "entity_module_status_change": {"type": "bool"}, + "entity_power_out_change": {"type": "bool"}, + "entity_power_status_change": {"type": "bool"}, + "entity_sensor": {"type": "bool"}, + "entity_unrecognised_module": {"type": "bool"}, + }, + }, + "feature_control": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "featureOpStatusChange": {"type": "bool"}, + "ciscoFeatOpStatusChange": {"type": "bool"}, + }, + }, + "generic": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "coldStart": {"type": "bool"}, + "warmStart": {"type": "bool"}, + }, + }, + "license": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "notify_license_expiry": {"type": "bool"}, + "notify_license_expiry_warning": {"type": "bool"}, + "notify_licensefile_missing": {"type": "bool"}, + "notify_no_license_for_feature": {"type": "bool"}, + }, + }, + "link": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "cErrDisableInterfaceEventRev1": {"type": "bool"}, + "cieLinkDown": {"type": "bool"}, + "cieLinkUp": {"type": "bool"}, + "cisco_xcvr_mon_status_chg": {"type": "bool"}, + "cmn_mac_move_notification": {"type": "bool"}, + "delayed_link_state_change": {"type": "bool"}, + "extended_linkDown": {"type": "bool"}, + "extended_linkUp": {"type": "bool"}, + "linkDown": {"type": "bool"}, + "linkUp": {"type": "bool"}, + }, + }, + "mmode": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "cseMaintModeChangeNotify": {"type": "bool"}, + "cseNormalModeChangeNotify": {"type": "bool"}, + }, + }, + "ospf": { + "type": "dict", + "options": {"enable": {"type": "bool"}}, + }, + "ospfv3": { + "type": "dict", + "options": {"enable": {"type": "bool"}}, + }, + "rf": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "redundancy_framework": {"type": "bool"}, + }, + }, + "rmon": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "fallingAlarm": {"type": "bool"}, + "hcFallingAlarm": {"type": "bool"}, + "hcRisingAlarm": {"type": "bool"}, + "risingAlarm": {"type": "bool"}, + }, + }, + "snmp": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "authentication": {"type": "bool"}, + }, + }, + "storm_control": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "cpscEventRev1": {"type": "bool"}, + "trap_rate": {"type": "bool"}, + }, + }, + "stpx": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "inconsistency": {"type": "bool"}, + "loop_inconsistency": {"type": "bool"}, + "root_inconsistency": {"type": "bool"}, + }, + }, + "syslog": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "message_generated": {"type": "bool"}, + }, + }, + "sysmgr": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "cseFailSwCoreNotifyExtended": {"type": "bool"}, + }, + }, + "system": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "clock_change_notification": {"type": "bool"}, + }, + }, + "upgrade": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "upgradeJobStatusNotify": {"type": "bool"}, + "upgradeOpNotifyOnCompletion": {"type": "bool"}, + }, + }, + "vtp": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "notifs": {"type": "bool"}, + "vlancreate": {"type": "bool"}, + "vlandelete": {"type": "bool"}, + }, + }, + }, + }, + "engine_id": { + "type": "dict", + "options": {"local": {"type": "str"}}, + }, + "global_enforce_priv": {"type": "bool"}, + "hosts": { + "type": "list", + "elements": "dict", + "options": { + "host": {"type": "str"}, + "community": {"type": "str"}, + "filter_vrf": {"type": "str"}, + "informs": {"type": "bool"}, + "source_interface": {"type": "str"}, + "traps": {"type": "bool"}, + "use_vrf": {"type": "str"}, + "version": { + "type": "str", + "choices": ["1", "2c", "3"], + }, + "auth": {"type": "str"}, + "priv": {"type": "str"}, + "udp_port": {"type": "int"}, + }, + }, + "location": {"type": "str"}, + "mib": { + "type": "dict", + "options": { + "community_map": { + "type": "dict", + "options": { + "community": {"type": "str"}, + "context": {"type": "str"}, + }, + }, + }, + }, + "packetsize": {"type": "int"}, + "protocol": { + "type": "dict", + "options": {"enable": {"type": "bool"}}, + }, + "source_interface": { + "type": "dict", + "options": { + "informs": {"type": "str"}, + "traps": {"type": "str"}, + }, + }, + "system_shutdown": {"type": "bool"}, + "tcp_session": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "auth": {"type": "bool"}, + }, + }, + "users": { + "type": "dict", + "options": { + "auth": { + "type": "list", + "elements": "dict", + "options": { + "user": {"type": "str"}, + "group": {"type": "str"}, + "authentication": { + "type": "dict", + "options": { + "algorithm": { + "type": "str", + "choices": [ + "md5", + "sha", + "sha-256", + ], + }, + "password": { + "type": "str", + "no_log": False, + }, + "engine_id": {"type": "str"}, + "localized_key": {"type": "bool"}, + "localizedv2_key": {"type": "bool"}, + "priv": { + "type": "dict", + "options": { + "privacy_password": { + "type": "str", + "no_log": False, + }, + "aes_128": {"type": "bool"}, + }, + }, + }, + }, + }, + }, + "use_acls": { + "type": "list", + "elements": "dict", + "options": { + "user": {"type": "str"}, + "ipv4": {"type": "str"}, + "ipv6": {"type": "str"}, + }, + }, + }, + }, + }, + }, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/static_routes.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/static_routes.py new file mode 100644 index 00000000..58a669bd --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/static_routes.py @@ -0,0 +1,89 @@ +# +# -*- 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 nxos_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 nxos_static_routes module""" + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "address_families": { + "elements": "dict", + "options": { + "afi": { + "choices": ["ipv4", "ipv6"], + "required": True, + "type": "str", + }, + "routes": { + "elements": "dict", + "options": { + "dest": {"required": True, "type": "str"}, + "next_hops": { + "elements": "dict", + "options": { + "admin_distance": {"type": "int"}, + "dest_vrf": {"type": "str"}, + "forward_router_address": {"type": "str"}, + "interface": {"type": "str"}, + "route_name": {"type": "str"}, + "tag": {"type": "int"}, + "track": {"type": "int"}, + }, + "type": "list", + }, + }, + "type": "list", + }, + }, + "type": "list", + }, + "vrf": {"type": "str"}, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py new file mode 100644 index 00000000..7da72979 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py @@ -0,0 +1,115 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# 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 nxos_telemetry module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class TelemetryArgs(object): # pylint: disable=R0903 + """The arg spec for the nxos_telemetry module""" + + argument_spec = { + "config": { + "options": { + "certificate": { + "options": { + "hostname": {"type": "str"}, + "key": {"type": "str", "no_log": False}, + }, + "type": "dict", + }, + "compression": {"choices": ["gzip"], "type": "str"}, + "source_interface": {"type": "str"}, + "vrf": {"type": "str"}, + "destination_groups": { + "options": { + "destination": { + "options": { + "encoding": { + "choices": ["GPB", "JSON"], + "type": "str", + }, + "ip": {"type": "str"}, + "port": {"type": "int"}, + "protocol": { + "choices": ["HTTP", "TCP", "UDP", "gRPC"], + "type": "str", + }, + }, + "type": "dict", + }, + "id": {"type": "str"}, + }, + "type": "list", + "elements": "raw", + }, + "sensor_groups": { + "options": { + "data_source": { + "choices": ["NX-API", "DME", "YANG"], + "type": "str", + }, + "id": {"type": "str"}, + "path": { + "options": { + "depth": {"type": "str"}, + "filter_condition": {"type": "str"}, + "name": {"type": "str"}, + "query_condition": {"type": "str"}, + }, + "type": "dict", + }, + }, + "type": "list", + "elements": "raw", + }, + "subscriptions": { + "options": { + "destination_group": {"type": "str"}, + "id": {"type": "str"}, + "sensor_group": { + "options": { + "id": {"type": "str"}, + "sample_interval": {"type": "int"}, + }, + "type": "dict", + }, + }, + "type": "list", + "elements": "raw", + }, + }, + "type": "dict", + }, + "state": { + "choices": ["merged", "replaced", "deleted", "gathered"], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/vlans.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/vlans.py new file mode 100644 index 00000000..4f915f09 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/vlans.py @@ -0,0 +1,64 @@ +# +# -*- 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 nxos_vlans module +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class VlansArgs(object): + """The arg spec for the nxos_vlans module""" + + argument_spec = { + "running_config": {"type": "str"}, + "config": { + "elements": "dict", + "options": { + "enabled": {"type": "bool"}, + "mapped_vni": {"type": "int"}, + "mode": {"choices": ["ce", "fabricpath"], "type": "str"}, + "name": {"type": "str"}, + "vlan_id": {"required": True, "type": "int"}, + "state": {"choices": ["active", "suspend"], "type": "str"}, + }, + "type": "list", + }, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/telemetry.py new file mode 100644 index 00000000..f5844538 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/telemetry.py @@ -0,0 +1,147 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Telemetry Command Reference File + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +TMS_GLOBAL = """ +# The cmd_ref is a yaml formatted list of module commands. +# A leading underscore denotes a non-command variable; e.g. _template. +# TMS does not have convenient global json data so this cmd_ref uses raw cli configs. +--- +_template: # _template holds common settings for all commands + # Enable feature telemetry if disabled + feature: telemetry + # Common get syntax for TMS commands + get_command: show run telemetry all + # Parent configuration for TMS commands + context: + - telemetry +certificate: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + kind: dict + getval: certificate (?P<key>\\S+) (?P<hostname>\\S+)$ + setval: certificate {key} {hostname} + default: + key: ~ + hostname: ~ +compression: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + kind: str + getval: use-compression (\\S+)$ + setval: 'use-compression {0}' + default: ~ + context: &dpcontext + - telemetry + - destination-profile +source_interface: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + kind: str + getval: source-interface (\\S+)$ + setval: 'source-interface {0}' + default: ~ + context: *dpcontext +vrf: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + kind: str + getval: use-vrf (\\S+)$ + setval: 'use-vrf {0}' + default: ~ + context: *dpcontext +""" + +TMS_DESTGROUP = """ +# The cmd_ref is a yaml formatted list of module commands. +# A leading underscore denotes a non-command variable; e.g. _template. +# TBD: Use Structured Where Possible +--- +_template: # _template holds common settings for all commands + # Enable feature telemetry if disabled + feature: telemetry + # Common get syntax for TMS commands + get_command: show run telemetry all + # Parent configuration for TMS commands + context: + - telemetry +destination: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + multiple: true + kind: dict + getval: ip address (?P<ip>\\S+) port (?P<port>\\S+) protocol (?P<protocol>\\S+) encoding (?P<encoding>\\S+) + setval: ip address {ip} port {port} protocol {protocol} encoding {encoding} + default: + ip: ~ + port: ~ + protocol: ~ + encoding: ~ +""" + +TMS_SENSORGROUP = """ +# The cmd_ref is a yaml formatted list of module commands. +# A leading underscore denotes a non-command variable; e.g. _template. +# TBD: Use Structured Where Possible +--- +_template: # _template holds common settings for all commands + # Enable feature telemetry if disabled + feature: telemetry + # Common get syntax for TMS commands + get_command: show run telemetry all + # Parent configuration for TMS commands + context: + - telemetry +data_source: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + kind: str + getval: data-source (\\S+)$ + setval: 'data-source {0}' + default: ~ +path: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + multiple: true + kind: dict + getval: path (?P<name>(\\S+|".*"))( depth (?P<depth>\\S+))?( query-condition (?P<query_condition>\\S+))?( filter-condition (?P<filter_condition>\\S+))?$ + setval: path {name} depth {depth} query-condition {query_condition} filter-condition {filter_condition} + default: + name: ~ + depth: ~ + query_condition: ~ + filter_condition: ~ +""" + +TMS_SUBSCRIPTION = """ +# The cmd_ref is a yaml formatted list of module commands. +# A leading underscore denotes a non-command variable; e.g. _template. +# TBD: Use Structured Where Possible +--- +_template: # _template holds common settings for all commands + # Enable feature telemetry if disabled + feature: telemetry + # Common get syntax for TMS commands + get_command: show run telemetry all + # Parent configuration for TMS commands + context: + - telemetry +destination_group: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + multiple: true + kind: str + getval: dst-grp (\\S+)$ + setval: 'dst-grp {0}' + default: ~ +sensor_group: + _exclude: ['N3K', 'N5K', 'N6k', 'N7k'] + multiple: true + kind: dict + getval: snsr-grp (?P<id>\\S+) sample-interval (?P<sample_interval>\\S+)$ + setval: snsr-grp {id} sample-interval {sample_interval} + default: + id: ~ + sample_interval: ~ +""" diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000..5bd15062 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py @@ -0,0 +1,321 @@ +# +# -*- 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 nxos_acl_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, + normalize_interface, + search_obj_in_list, +) + + +class Acl_interfaces(ConfigBase): + """ + The nxos_acl_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["acl_interfaces"] + + def __init__(self, module): + super(Acl_interfaces, self).__init__(module) + + def get_acl_interfaces_facts(self, data=None): + """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, + data=data, + ) + acl_interfaces_facts = facts["ansible_network_resources"].get("acl_interfaces") + if not acl_interfaces_facts: + return [] + return acl_interfaces_facts + + def edit_config(self, commands): + """Wrapper method for `_connection.edit_config()` + This exists solely to allow the unit test framework to mock device connection calls. + """ + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + self.state = self._module.params["state"] + action_states = ["merged", "replaced", "deleted", "overridden"] + + if self.state == "gathered": + result["gathered"] = self.get_acl_interfaces_facts() + elif self.state == "rendered": + result["rendered"] = self.set_config({}) + # no need to fetch facts for rendered + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.set_config({}) + # no need to fetch facts for parsed + else: + existing_acl_interfaces_facts = self.get_acl_interfaces_facts() + commands.extend(self.set_config(existing_acl_interfaces_facts)) + if commands and self.state in action_states: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + result["before"] = existing_acl_interfaces_facts + result["commands"] = commands + + changed_acl_interfaces_facts = self.get_acl_interfaces_facts() + if result["changed"]: + result["after"] = changed_acl_interfaces_facts + result["warnings"] = warnings + return result + + def set_config(self, existing_acl_interfaces_facts): + """Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + config = self._module.params["config"] + want = [] + if config: + for w in config: + if get_interface_type(w["name"]) == "loopback": + self._module.fail_json( + msg="This module works with ethernet, management or port-channe", + ) + w.update({"name": normalize_interface(w["name"])}) + want.append(remove_empties(w)) + have = existing_acl_interfaces_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + if self.state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(self.state), + ) + + commands = [] + if self.state == "overridden": + commands = self._state_overridden(want, have) + elif self.state == "deleted": + commands = self._state_deleted(want, have) + elif self.state == "rendered": + commands = self._state_rendered(want) + elif self.state == "parsed": + want = self._module.params["running_config"] + commands = self._state_parsed(want) + else: + for w in want: + if self.state == "merged": + commands.extend(self._state_merged(w, have)) + elif self.state == "replaced": + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_parsed(self, want): + return self.get_acl_interfaces_facts(want) + + def _state_rendered(self, want): + commands = [] + for w in want: + commands.extend(self.set_commands(w, {})) + return commands + + def _state_replaced(self, want, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + new_commands = [] + del_dict = {"name": want["name"], "access_groups": []} + obj_in_have = search_obj_in_list(want["name"], have, "name") + if obj_in_have != want: + commands = [] + if obj_in_have and "access_groups" in obj_in_have.keys(): + for ag in obj_in_have["access_groups"]: + want_afi = [] + if want.get("access_groups"): + want_afi = search_obj_in_list(ag["afi"], want["access_groups"], "afi") + if not want_afi: + # whatever in have is not in want + del_dict["access_groups"].append(ag) + else: + del_acl = [] + for acl in ag["acls"]: + if want_afi.get("acls"): + if acl not in want_afi["acls"]: + del_acl.append(acl) + else: + del_acl.append(acl) + afi = want_afi["afi"] + del_dict["access_groups"].append({"afi": afi, "acls": del_acl}) + + commands.extend(self._state_deleted([del_dict], have)) + commands.extend(self._state_merged(want, have)) + new_commands.append(commands[0]) + commands = [commands[i] for i in range(1, len(commands)) if commands[i] != commands[0]] + new_commands.extend(commands) + return new_commands + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want_intf = [w["name"] for w in want] + for h in have: + if h["name"] not in want_intf: + commands.extend(self._state_deleted([h], have)) + for w in want: + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_merged(self, want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(want, have) + + def set_commands(self, want, have, deleted=False): + commands = [] + have_name = search_obj_in_list(want["name"], have, "name") + if have_name and have_name.get("access_groups"): + if want.get("access_groups"): + for w_afi in want["access_groups"]: + ip = "ipv6" + if w_afi["afi"] == "ipv4": + ip = "ip" + have_afi = search_obj_in_list(w_afi["afi"], have_name["access_groups"], "afi") + if have_afi: + new_acls = [] + if deleted: + if w_afi.get("acls") and have_afi.get("acls"): + new_acls = [ + acl for acl in w_afi.get("acls") if acl in have_afi.get("acls") + ] + elif "acls" not in w_afi.keys(): + new_acls = have_afi.get("acls") + else: + if w_afi.get("acls"): + new_acls = [ + acl for acl in w_afi["acls"] if acl not in have_afi["acls"] + ] + commands.extend(self.process_acl(new_acls, ip, deleted)) + else: + if not deleted: + if w_afi.get("acls"): + commands.extend(self.process_acl(w_afi["acls"], ip)) + else: + # only name is given to delete + if deleted and "access_groups" in have_name.keys(): + commands.extend(self.process_access_group(have_name, True)) + else: + if not deleted: # and 'access_groups' in have_name.keys(): + commands.extend(self.process_access_group(want)) + + if len(commands) > 0: + commands.insert(0, "interface " + want["name"]) + return commands + + def process_access_group(self, item, deleted=False): + commands = [] + for ag in item["access_groups"]: + ip = "ipv6" + if ag["afi"] == "ipv4": + ip = "ip" + if ag.get("acls"): + commands.extend(self.process_acl(ag["acls"], ip, deleted)) + return commands + + def process_acl(self, acls, ip, deleted=False): + commands = [] + no = "" + if deleted: + no = "no " + for acl in acls: + port = "" + if acl.get("port"): + port = " port" + ag = " access-group " + if ip == "ipv6": + ag = " traffic-filter " + commands.append(no + ip + port + ag + acl["name"] + " " + acl["direction"]) + return commands + + def _state_deleted(self, main_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 = [] + if main_want: + if self.state == "deleted": + for w in main_want: + h = search_obj_in_list(w["name"], have, "name") or {} + commands.extend(self.set_commands(h, have, deleted=True)) + else: + for want in main_want: + commands.extend(self.set_commands(want, have, deleted=True)) + else: + for h in have: + commands.extend(self.set_commands(h, have, deleted=True)) + + return commands diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/acls.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/acls.py new file mode 100644 index 00000000..5e6f3c34 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/acls.py @@ -0,0 +1,674 @@ +# +# -*- 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 nxos_acls class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acls.acls import ( + AclsArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + search_obj_in_list, +) + + +class Acls(ConfigBase): + """ + The nxos_acls class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["acls"] + + def __init__(self, module): + super(Acls, self).__init__(module) + self.state = self._module.params["state"] + + def get_acls_facts(self, data=None): + """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, + data=data, + ) + acls_facts = facts["ansible_network_resources"].get("acls") + if not acls_facts: + return [] + return acls_facts + + def edit_config(self, commands): + """Wrapper method for `_connection.edit_config()` + This exists solely to allow the unit test framework to mock device connection calls. + """ + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + state = self.state + action_states = ["merged", "replaced", "deleted", "overridden"] + + if state == "gathered": + result["gathered"] = self.get_acls_facts() + elif state == "rendered": + result["rendered"] = self.set_config({}) + elif state == "parsed": + result["parsed"] = self.set_config({}) + else: + existing_acls_facts = self.get_acls_facts() + commands.extend(self.set_config(existing_acls_facts)) + result["before"] = existing_acls_facts + if commands and state in action_states: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + result["commands"] = commands + + changed_acls_facts = self.get_acls_facts() + if result["changed"]: + result["after"] = changed_acls_facts + result["warnings"] = warnings + return result + + def set_config(self, existing_acls_facts): + """Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + config = self._module.params["config"] + want = [] + if config: + for w in config: + want.append(remove_empties(w)) + have = existing_acls_facts + if want: + want = self.convert_values(want) + resp = self.set_state(want, have) + return to_list(resp) + + def convert_values(self, want): + """ + This method is used to map and convert the user given values with what will actually be present in the device configuation + """ + port_protocol = { + 515: "lpd", + 517: "talk", + 7: "echo", + 9: "discard", + 12: "exec", + 13: "login", + 14: "cmd", + 109: "pop2", + 19: "chargen", + 20: "ftp-data", + 21: "ftp", + 23: "telnet", + 25: "smtp", + 540: "uucp", + 543: "klogin", + 544: "kshell", + 37: "time", + 43: "whois", + 49: "tacacs", + 179: "bgp", + 53: "domain", + 194: "irc", + 70: "gopher", + 79: "finger", + 80: "www", + 101: "hostname", + 3949: "drip", + 110: "pop3", + 111: "sunrpc", + 496: "pim-auto-rp", + 113: "ident", + 119: "nntp", + } + protocol = { + 1: "icmp", + 2: "igmp", + 4: "ip", + 6: "tcp", + 103: "pim", + 108: "pcp", + 47: "gre", + 17: "udp", + 50: "esp", + 51: "ahp", + 88: "eigrp", + 89: "ospf", + 94: "nos", + } + precedence = { + 0: "routine", + 1: "priority", + 2: "immediate", + 3: "flash", + 4: "flash-override", + 5: "critical", + 6: "internet", + 7: "network", + } + dscp = { + 10: "AF11", + 12: "AF12", + 14: "AF13", + 18: "AF21", + 20: "AF22", + 22: "AF23", + 26: "AF31", + 28: "AF32", + 30: "AF33", + 34: "AF41", + 36: "AF42", + 38: "AF43", + 8: "CS1", + 16: "CS2", + 24: "CS3", + 32: "CS4", + 40: "CS5", + 48: "CS6", + 56: "CS7", + 0: "Default", + 46: "EF", + } + # port_pro_num = list(protocol.keys()) + for afi in want: + if "acls" in afi.keys(): + for acl in afi["acls"]: + if "aces" in acl.keys(): + for ace in acl["aces"]: + if "dscp" in ace.keys(): + if ace["dscp"] in dscp: + ace["dscp"] = dscp[int(ace["dscp"])] + if not ace["dscp"].isdigit(): + ace["dscp"] = ace["dscp"].lower() + if "precedence" in ace.keys(): + if ace["precedence"].isdigit(): + ace["precedence"] = precedence[int(ace["precedence"])] + if ( + "protocol" in ace.keys() + and ace["protocol"].isdigit() + and int(ace["protocol"]) in protocol.keys() + ): + ace["protocol"] = protocol[int(ace["protocol"])] + # convert number to name + if "protocol" in ace.keys() and ace["protocol"] in ["tcp", "udp"]: + for x in ["source", "destination"]: + if "port_protocol" in ace[x].keys(): + key = list(ace[x]["port_protocol"].keys())[0] + # key could be eq,gt,lt,neq or range + if key != "range": + val = ace[x]["port_protocol"][key] + if val.isdigit() and int(val) in port_protocol.keys(): + ace[x]["port_protocol"][key] = port_protocol[ + int(val) + ] + else: + st = int(ace[x]["port_protocol"]["range"]["start"]) + end = int(ace[x]["port_protocol"]["range"]["end"]) + + if st in port_protocol.keys(): + ace[x]["port_protocol"]["range"][ + "start" + ] = port_protocol[st] + if end in port_protocol.keys(): + ace[x]["port_protocol"]["range"][ + "end" + ] = port_protocol[end] + return want + + 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.state + commands = [] + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "rendered": + commands = self._state_rendered(want) + elif state == "parsed": + want = self._module.params["running_config"] + commands = self._state_parsed(want) + else: + for w in want: + if state == "merged": + commands.extend(self._state_merged(w, have)) + elif state == "replaced": + commands.extend(self._state_replaced(w, have)) + if state != "parsed": + commands = [c.strip() for c in commands] + return commands + + def _state_parsed(self, want): + return self.get_acls_facts(want) + + def _state_rendered(self, want): + commands = [] + for w in want: + commands.extend(self.set_commands(w, {})) + return commands + + def _state_replaced(self, want, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + have_afi = search_obj_in_list(want["afi"], have, "afi") + del_dict = {"acls": []} + want_names = [] + if have_afi != want: + if have_afi: + del_dict.update({"afi": have_afi["afi"], "acls": []}) + if want.get("acls"): + want_names = [w["name"] for w in want["acls"]] + have_names = [h["name"] for h in have_afi["acls"]] + want_acls = want.get("acls") + for w in want_acls: + acl_commands = [] + if w["name"] not in have_names: + # creates new ACL in replaced state + merge_dict = {"afi": want["afi"], "acls": [w]} + commands.extend(self._state_merged(merge_dict, have)) + else: + # acl in want exists in have + have_name = search_obj_in_list(w["name"], have_afi["acls"], "name") + have_aces = have_name.get("aces") if have_name.get("aces") else [] + merge_aces = [] + del_aces = [] + w_aces = w.get("aces") if w.get("aces") else [] + + for ace in have_aces: + if ace not in w_aces: + del_aces.append(ace) + for ace in w_aces: + if ace not in have_aces: + merge_aces.append(ace) + merge_dict = { + "afi": want["afi"], + "acls": [{"name": w["name"], "aces": merge_aces}], + } + del_dict = { + "afi": want["afi"], + "acls": [{"name": w["name"], "aces": del_aces}], + } + if del_dict["acls"]: + acl_commands.extend(self._state_deleted([del_dict], have)) + acl_commands.extend(self._state_merged(merge_dict, have)) + + for i in range(1, len(acl_commands)): + if acl_commands[i] == acl_commands[0]: + acl_commands[i] = "" + commands.extend(acl_commands) + else: + acls = [] + # no acls given in want, so delete all have acls + for acl in have_afi["acls"]: + acls.append({"name": acl["name"]}) + del_dict["acls"] = acls + if del_dict["acls"]: + commands.extend(self._state_deleted([del_dict], have)) + + else: + # want_afi is not present in have + commands.extend(self._state_merged(want, have)) + + commands = list(filter(None, commands)) + return commands + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want_afi = [w["afi"] for w in want] + for h in have: + if h["afi"] in want_afi: + w = search_obj_in_list(h["afi"], want, "afi") + for h_acl in h["acls"]: + w_acl = search_obj_in_list(h_acl["name"], w["acls"], "name") + if not w_acl: + del_dict = { + "afi": h["afi"], + "acls": [{"name": h_acl["name"]}], + } + commands.extend(self._state_deleted([del_dict], have)) + else: + # if afi is not in want + commands.extend(self._state_deleted([{"afi": h["afi"]}], have)) + for w in want: + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_merged(self, want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(want, have) + + 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 = [] + if want: # and have != want: + for w in want: + ip = "ipv6" if w["afi"] == "ipv6" else "ip" + acl_names = [] + have_afi = search_obj_in_list(w["afi"], have, "afi") + # if want['afi] not in have, ignore + if have_afi: + if w.get("acls"): + for acl in w["acls"]: + if "aces" in acl.keys() and self.state != "deleted": + have_name = search_obj_in_list( + acl["name"], + have_afi["acls"], + "name", + ) + if have_name: + ace_commands = [] + flag = 0 + for ace in acl["aces"]: + if list(ace.keys()) == ["sequence"]: + # only sequence number is specified to be deleted + if "aces" in have_name.keys(): + for h_ace in have_name["aces"]: + if h_ace["sequence"] == ace["sequence"]: + ace_commands.append( + "no " + str(ace["sequence"]), + ) + flag = 1 + else: + if "aces" in have_name.keys(): + for h_ace in have_name["aces"]: + # when want['ace'] does not have seq number + if "sequence" not in ace.keys(): + del h_ace["sequence"] + if ace == h_ace: + ace_commands.append( + "no " + self.process_ace(ace), + ) + flag = 1 + if flag: + ace_commands.insert( + 0, + ip + " access-list " + acl["name"], + ) + commands.extend(ace_commands) + else: + # only name given + for h in have_afi["acls"]: + if h["name"] == acl["name"]: + acl_names.append(acl["name"]) + for name in acl_names: + commands.append("no " + ip + " access-list " + name) + + else: + # 'only afi is given' + if have_afi.get("acls"): + for h in have_afi["acls"]: + acl_names.append(h["name"]) + for name in acl_names: + commands.append("no " + ip + " access-list " + name) + else: + v6 = [] + v4 = [] + v6_local = v4_local = None + for h in have: + if h["afi"] == "ipv6": + v6 = (acl["name"] for acl in h["acls"]) + if "match_local_traffic" in h.keys(): + v6_local = True + else: + v4 = (acl["name"] for acl in h["acls"]) + if "match_local_traffic" in h.keys(): + v4_local = True + + self.no_commands(v4, commands, v4_local, "ip") + self.no_commands(v6, commands, v6_local, "ipv6") + + for name in v6: + commands.append("no ipv6 access-list " + name) + if v4_local: + commands.append("no ipv6 access-list match-local-traffic") + + return commands + + def no_commands(self, v_list, commands, match_local, ip): + for name in v_list: + commands.append("no " + ip + " access-list " + name) + if match_local: + commands.append("no " + ip + " access-list match-local-traffic") + + def set_commands(self, want, have): + commands = [] + have_afi = search_obj_in_list(want["afi"], have, "afi") + ip = "" + if "v6" in want["afi"]: + ip = "ipv6 " + else: + ip = "ip " + + if have_afi: + if want.get("acls"): + for w_acl in want["acls"]: + have_acl = search_obj_in_list(w_acl["name"], have_afi["acls"], "name") + name = w_acl["name"] + flag = 0 + ace_commands = [] + if have_acl != w_acl: + if have_acl: + ace_list = [] + if w_acl.get("aces") and have_acl.get("aces"): + # case 1 --> sequence number not given in want --> new ace + # case 2 --> new sequence number in want --> new ace + # case 3 --> existing sequence number given --> update rule (only for merged state. + # For replaced and overridden, rule is deleted in the state's config) + + ace_list = [ + item for item in w_acl["aces"] if "sequence" not in item.keys() + ] # case 1 + + want_seq = [ + item["sequence"] + for item in w_acl["aces"] + if "sequence" in item.keys() + ] + + have_seq = [item["sequence"] for item in have_acl["aces"]] + + new_seq = list(set(want_seq) - set(have_seq)) + common_seq = list(set(want_seq).intersection(set(have_seq))) + + temp_list = [ + item + for item in w_acl["aces"] + if "sequence" in item.keys() and item["sequence"] in new_seq + ] # case 2 + ace_list.extend(temp_list) + for w in w_acl["aces"]: + self.argument_spec = AclsArgs.argument_spec + params = utils.validate_config( + self.argument_spec, + { + "config": [ + { + "afi": want["afi"], + "acls": [ + { + "name": name, + "aces": ace_list, + }, + ], + }, + ], + }, + ) + if "sequence" in w.keys() and w["sequence"] in common_seq: + temp_obj = search_obj_in_list( + w["sequence"], + have_acl["aces"], + "sequence", + ) # case 3 + if temp_obj != w: + ace_list.append(w) + if self.state == "merged": + # merged will never negate commands + self._module.fail_json( + msg="Cannot update existing ACE {0} of ACL {1} with state merged." + " Please use state replaced or overridden.".format( + name, + w["sequence"], + ), + ) + elif w_acl.get("aces"): + # 'have' has ACL defined without any ACE + ace_list = list(w_acl["aces"]) + for w_ace in ace_list: + ace_commands.append(self.process_ace(w_ace).strip()) + flag = 1 + + if flag: + ace_commands.insert(0, ip + "access-list " + name) + + else: + commands.append(ip + "access-list " + name) + if "aces" in w_acl.keys(): + for w_ace in w_acl["aces"]: + commands.append(self.process_ace(w_ace).strip()) + commands.extend(ace_commands) + else: + if want.get("acls"): + for w_acl in want["acls"]: + name = w_acl["name"] + commands.append(ip + "access-list " + name) + if "aces" in w_acl.keys(): + for w_ace in w_acl["aces"]: + commands.append(self.process_ace(w_ace).strip()) + + return commands + + def process_ace(self, w_ace): + command = "" + ace_keys = w_ace.keys() + if "remark" in ace_keys: + command += "remark " + w_ace["remark"] + " " + else: + command += w_ace["grant"] + " " + if "protocol" in ace_keys: + if w_ace["protocol"] == "icmpv6": + command += "icmp" + " " + else: + command += w_ace["protocol"] + " " + src = self.get_address(w_ace["source"], w_ace["protocol"]) + dest = self.get_address(w_ace["destination"], w_ace["protocol"]) + command += src + dest + if "protocol_options" in ace_keys: + pro = list(w_ace["protocol_options"].keys())[0] + if pro != w_ace["protocol"]: + self._module.fail_json(msg="protocol and protocol_options mismatch") + flags = "" + for k in w_ace["protocol_options"][pro].keys(): + if k not in ["telemetry_queue", "telemetry_path"]: + k = re.sub("_", "-", k) + flags += k + " " + command += flags + if "dscp" in ace_keys: + command += "dscp " + w_ace["dscp"] + " " + if "fragments" in ace_keys: + command += "fragments " + if "precedence" in ace_keys: + command += "precedence " + w_ace["precedence"] + " " + if "log" in ace_keys: + command += "log " + if "sequence" in ace_keys: + command = str(w_ace["sequence"]) + " " + command + return command + + def get_address(self, endpoint, pro=""): + ret_addr = "" + keys = list(endpoint.keys()) + if "address" in keys: + if "wildcard_bits" not in keys: + self._module.fail_json(msg="wildcard bits not specified for address") + else: + ret_addr = endpoint["address"] + " " + endpoint["wildcard_bits"] + " " + elif "any" in keys: + ret_addr = "any " + elif "host" in keys: + ret_addr = "host " + endpoint["host"] + " " + elif "prefix" in keys: + ret_addr = endpoint["prefix"] + " " + + if pro in ["tcp", "udp"]: + if "port_protocol" in keys: + options = self.get_options(endpoint["port_protocol"]) + ret_addr += options + return ret_addr + + def get_options(self, item): + com = "" + subkey = list(item.keys()) + if "range" in subkey: + com = "range " + item["range"]["start"] + " " + item["range"]["end"] + " " + else: + com = subkey[0] + " " + item[subkey[0]] + " " + return com diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/bfd_interfaces.py new file mode 100644 index 00000000..a9dc51fd --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/bfd_interfaces.py @@ -0,0 +1,311 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +nxos_bfd_interfaces class +This class creates a command set to bring the current device configuration +to a desired end-state. The command set is based on a comparison of the +current configuration (as dict) and the provided configuration (as dict). +""" +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 ( + dict_diff, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + flatten_dict, + search_obj_in_list, +) + + +class Bfd_interfaces(ConfigBase): + """ + The nxos_bfd_interfaces class + """ + + gather_subset = ["min"] + gather_network_resources = ["bfd_interfaces"] + # exclude_params = [] + + def __init__(self, module): + super(Bfd_interfaces, self).__init__(module) + + def get_bfd_interfaces_facts(self, data=None): + """Get the 'facts' (the current configuration) + + :returns: A list of interface configs and a platform string + """ + if self.state not in self.ACTION_STATES: + self.gather_subset = ["!all", "!min"] + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, + self.gather_network_resources, + data=data, + ) + bfd_interfaces_facts = facts["ansible_network_resources"].get("bfd_interfaces", []) + + platform = facts.get("ansible_net_platform", "") + return bfd_interfaces_facts, platform + + def edit_config(self, commands): + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + + if self.state in self.ACTION_STATES: + ( + existing_bfd_interfaces_facts, + platform, + ) = self.get_bfd_interfaces_facts() + else: + existing_bfd_interfaces_facts, platform = [], "" + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_bfd_interfaces_facts, platform)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + ( + changed_bfd_interfaces_facts, + platform, + ) = self.get_bfd_interfaces_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"], platform = self.get_bfd_interfaces_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_bfd_interfaces_facts + if result["changed"]: + result["after"] = changed_bfd_interfaces_facts + + elif self.state == "gathered": + result["gathered"] = changed_bfd_interfaces_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_bfd_interfaces_facts, platform): + """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 + """ + if re.search("N[56]K", platform): + # Some platforms do not support the 'bfd' interface keyword; + # remove the 'bfd' key from each want/have interface. + orig_want = self._module.params["config"] + want = [] + for w in orig_want: + del w["bfd"] + want.append(w) + orig_have = existing_bfd_interfaces_facts + have = [] + for h in orig_have: + del h["bfd"] + have.append(h) + else: + want = self._module.params["config"] + have = existing_bfd_interfaces_facts + + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params["state"] + if state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + + cmds = list() + if state == "overridden": + cmds.extend(self._state_overridden(want, have)) + elif state == "deleted": + cmds.extend(self._state_deleted(want, have)) + else: + for w in want: + if state in ["merged", "rendered"]: + cmds.extend(self._state_merged(flatten_dict(w), have)) + elif state == "replaced": + cmds.extend(self._state_replaced(flatten_dict(w), have)) + return cmds + + def _state_replaced(self, want, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + cmds = [] + obj_in_have = search_obj_in_list(want["name"], have, "name") + if obj_in_have: + diff = dict_diff(want, obj_in_have) + else: + diff = want + merged_cmds = self.set_commands(want, have) + if "name" not in diff: + diff["name"] = want["name"] + + replaced_cmds = [] + if obj_in_have: + replaced_cmds = self.del_attribs(diff) + if replaced_cmds or merged_cmds: + for cmd in set(replaced_cmds).intersection(set(merged_cmds)): + merged_cmds.remove(cmd) + cmds.extend(replaced_cmds) + cmds.extend(merged_cmds) + return cmds + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + cmds = [] + for h in have: + # Clean up bfd attrs for any interfaces not listed in the play + h = flatten_dict(h) + obj_in_want = flatten_dict(search_obj_in_list(h["name"], want, "name")) + if obj_in_want: + # Let the 'want' loop handle all vals for this interface + continue + cmds.extend(self.del_attribs(h)) + for w in want: + # Update any want attrs if needed. The overridden state considers + # the play as the source of truth for the entire device, therefore + # set any unspecified attrs to their default state. + w = self.set_none_vals_to_defaults(flatten_dict(w)) + cmds.extend(self.set_commands(w, have)) + return cmds + + def _state_merged(self, want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(want, have) + + 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 + """ + if not (want or have): + return [] + cmds = [] + if want: + for w in want: + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + cmds.extend(self.del_attribs(obj_in_have)) + else: + for h in have: + cmds.extend(self.del_attribs(flatten_dict(h))) + return cmds + + def del_attribs(self, obj): + if not obj or len(obj.keys()) == 1: + return [] + cmds = [] + # 'bfd' and 'bfd echo' are enabled by default so the handling is + # counter-intuitive; we are enabling them to remove them. The end result + # is that they are removed from the interface config on the device. + if "bfd" in obj and "disable" in obj["bfd"]: + cmds.append("bfd") + if "echo" in obj and "disable" in obj["echo"]: + cmds.append("bfd echo") + if cmds: + cmds.insert(0, "interface " + obj["name"]) + return cmds + + def set_none_vals_to_defaults(self, want): + # Set dict None values to default states + if "bfd" in want and want["bfd"] is None: + want["bfd"] = "enable" + if "echo" in want and want["echo"] is None: + want["echo"] = "enable" + return want + + def diff_of_dicts(self, want, obj_in_have): + diff = set(want.items()) - set(obj_in_have.items()) + diff = dict(diff) + if diff and want["name"] == obj_in_have["name"]: + diff.update({"name": want["name"]}) + return diff + + def add_commands(self, want): + if not want: + return [] + cmds = [] + if "bfd" in want and want["bfd"] is not None: + cmd = "bfd" if want["bfd"] == "enable" else "no bfd" + cmds.append(cmd) + if "echo" in want and want["echo"] is not None: + cmd = "bfd echo" if want["echo"] == "enable" else "no bfd echo" + cmds.append(cmd) + + if cmds: + cmds.insert(0, "interface " + want["name"]) + return cmds + + def set_commands(self, want, have): + cmds = [] + obj_in_have = flatten_dict(search_obj_in_list(want["name"], have, "name")) + if not obj_in_have: + cmds = self.add_commands(want) + else: + diff = self.diff_of_dicts(want, obj_in_have) + cmds = self.add_commands(diff) + return cmds diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/bgp_address_family.py new file mode 100644 index 00000000..4443039c --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/bgp_address_family.py @@ -0,0 +1,253 @@ +# +# -*- 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) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_bgp_address_family config file. +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 its desired end-state is +created. +""" + +from copy import deepcopy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + remove_empties, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_address_family import ( + Bgp_address_familyTemplate, +) + + +class Bgp_address_family(ResourceModule): + """ + The nxos_bgp_address_family config class + """ + + def __init__(self, module): + super(Bgp_address_family, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="bgp_address_family", + tmplt=Bgp_address_familyTemplate(), + ) + self.parsers = [ + "additional_paths.install_backup", + "additional_paths.receive", + "additional_paths.selection.route_map", + "additional_paths.send", + "advertise_l2vpn_evpn", + "advertise_pip", + "advertise_system_mac", + "allow_vni_in_ethertag", + "client_to_client.no_reflection", + "dampen_igp_metric", + "dampening", + "default_information.originate", + "default_metric", + "distance", + "export_gateway_ip", + "maximum_paths.parallel_paths", + "maximum_paths.ibgp.parallel_paths", + "maximum_paths.eibgp.parallel_paths", + "maximum_paths.local.parallel_paths", + "maximum_paths.mixed.parallel_paths", + "nexthop.route_map", + "nexthop.trigger_delay", + "retain.route_target.retain_all", + "retain.route_target.route_map", + "suppress_inactive", + "table_map", + "timers.bestpath_defer", + "wait_igp_convergence", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = deepcopy(self.want) + haved = deepcopy(self.have) + + self._bgp_af_list_to_dict(wantd) + self._bgp_af_list_to_dict(haved) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + wantd = remove_empties(wantd) + haved = remove_empties(haved) + + have_af = haved.get("address_family", {}) + want_af = wantd.get("address_family", {}) + wvrfs = wantd.get("vrfs", {}) + hvrfs = haved.get("vrfs", {}) + + # if state is overridden or deleted, remove superfluos config + if self.state in ["deleted", "overridden"]: + if (haved and haved["as_number"] == wantd.get("as_number")) or not wantd: + remove = True if self.state == "deleted" else False + purge = True if not wantd else False + self._remove_af(want_af, have_af, remove=remove, purge=purge) + + for k, hvrf in iteritems(hvrfs): + wvrf = wvrfs.get(k, {}) + self._remove_af(wvrf, hvrf, vrf=k, remove=remove, purge=purge) + + if self.state in ["merged", "replaced", "overridden", "rendered"]: + for k, want in iteritems(want_af): + self._compare(want=want, have=have_af.pop(k, {})) + + # handle vrf->af + for wk, wvrf in iteritems(wvrfs): + cur_ptr = len(self.commands) + + hvrf = hvrfs.pop(wk, {}) + for k, want in iteritems(wvrf): + self._compare(want=want, have=hvrf.pop(k, {})) + + # add VRF command at correct position once + if cur_ptr != len(self.commands): + self.commands.insert(cur_ptr, "vrf {0}".format(wk)) + + if self.commands: + self.commands.insert(0, "router bgp {as_number}".format(**haved or wantd)) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Bgp_address_family network resource. + """ + begin = len(self.commands) + + self.compare(parsers=self.parsers, want=want, have=have) + self._compare_lists(want=want, have=have) + + if len(self.commands) != begin or (not have and want): + self.commands.insert( + begin, + self._tmplt.render(want or have, "address_family", False), + ) + + def _compare_lists(self, want, have): + for attrib in [ + "aggregate_address", + "inject_map", + "networks", + "redistribute", + ]: + wdict = want.get(attrib, {}) + hdict = have.get(attrib, {}) + for key, entry in iteritems(wdict): + if entry != hdict.pop(key, {}): + self.addcmd(entry, attrib.format(attrib), False) + + # remove remaining items in have for replaced + for entry in hdict.values(): + self.addcmd(entry, attrib.format(attrib), True) + + def _bgp_af_list_to_dict(self, entry): + def _build_key(data): + """Build primary key for each dict + + :params x: dictionary + :returns: primary key as tuple + """ + # afi should always be present + # safi and vrf are optional + # a combination of these 3 uniquely + # identifies an AF context + afi = "afi_" + data["afi"] + safi = "safi_" + data.get("safi", "") + vrf = "vrf_" + data.get("vrf", "") + + return (afi, safi, vrf) + + # transform parameters which are + # list of dicts to dict of dicts + for item in entry.get("address_family", []): + item["aggregate_address"] = {x["prefix"]: x for x in item.get("aggregate_address", [])} + item["inject_map"] = { + (x["route_map"], x["exist_map"]): x for x in item.get("inject_map", []) + } + item["networks"] = {x["prefix"]: x for x in item.get("networks", [])} + item["redistribute"] = { + (x.get("id"), x["protocol"]): x for x in item.get("redistribute", []) + } + + # transform all entries under + # config->address_family to dict of dicts + af = {_build_key(x): x for x in entry.get("address_family", [])} + + temp = {} + entry["vrfs"] = {} + entry["address_family"] = {} + + # group AFs by VRFs + # vrf_ denotes global AFs + for k in af.keys(): + for x in k: + if x.startswith("vrf_"): + if x not in temp: + temp[x] = {} + temp[x][k] = af[k] + + for k in temp.keys(): + if k == "vrf_": + # populate global AFs + entry["address_family"][k] = temp[k] + else: + # populate VRF AFs + entry["vrfs"][k.replace("vrf_", "", 1)] = temp[k] + + entry["address_family"] = entry["address_family"].get("vrf_", {}) + + # final structure: https://gist.github.com/NilashishC/628dae5fe39a4908e87c9e833bfbe57d + + def _remove_af(self, want_af, have_af, vrf=None, remove=False, purge=False): + cur_ptr = len(self.commands) + for k, v in iteritems(have_af): + # first conditional is for deleted with config provided + # second conditional is for overridden + # third condition is for deleted with empty config + if any( + ( + (remove and k in want_af), + (not remove and k not in want_af), + purge, + ), + ): + self.addcmd(v, "address_family", True) + if cur_ptr < len(self.commands) and vrf: + self.commands.insert(cur_ptr, "vrf {0}".format(vrf)) + self.commands.append("exit") diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/bgp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/bgp_global.py new file mode 100644 index 00000000..edb6e59b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/bgp_global.py @@ -0,0 +1,410 @@ +# +# -*- 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) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_bgp_global config file. +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 its desired end-state is +created. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_global import ( + Bgp_globalTemplate, +) + + +class Bgp_global(ResourceModule): + """ + The nxos_bgp_global config class + """ + + def __init__(self, module): + super(Bgp_global, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="bgp_global", + tmplt=Bgp_globalTemplate(), + ) + # VRF parsers = 29 + self.parsers = [ + "allocate_index", + "affinity_group.group_id", + "bestpath.always_compare_med", + "bestpath.as_path.ignore", + "bestpath.as_path.multipath_relax", + "bestpath.compare_neighborid", + "bestpath.compare_routerid", + "bestpath.cost_community_ignore", + "bestpath.igp_metric_ignore", + "bestpath.med.confed", + "bestpath.med.missing_as_worst", + "bestpath.med.non_deterministic", + "cluster_id", + "local_as", + "confederation.identifier", + "graceful_restart", + "graceful_restart.restart_time", + "graceful_restart.stalepath_time", + "graceful_restart.helper", + "log_neighbor_changes", + "maxas_limit", + "neighbor_down.fib_accelerate", + "reconnect_interval", + "router_id", + "timers.bestpath_limit", + "timers.bgp", + "timers.prefix_peer_timeout", + "timers.prefix_peer_wait", + # end VRF parsers + "disable_policy_batching", + "disable_policy_batching.ipv4.prefix_list", + "disable_policy_batching.ipv6.prefix_list", + "disable_policy_batching.nexthop", + "dynamic_med_interval", + "enforce_first_as", + "enhanced_error", + "fast_external_fallover", + "flush_routes", + "graceful_shutdown.activate", + "graceful_shutdown.aware", + "isolate", + "nexthop.suppress_default_resolution", + "shutdown", + "suppress_fib_pending", + "fabric_soo", + "rd", + ] + self._af_data = {} + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + # we fail early if state is merged or + # replaced and want ASN != have ASN + if self.state in ["merged", "replaced"]: + w_asn = self.want.get("as_number") + h_asn = self.have.get("as_number") + + if h_asn and w_asn != h_asn: + self._module.fail_json( + msg="BGP is already configured with ASN {0}. " + "Please remove it with state purged before " + "configuring new ASN".format(h_asn), + ) + + if self.state in ["deleted", "replaced"]: + self._build_af_data() + + for entry in self.want, self.have: + self._bgp_list_to_dict(entry) + + # if state is deleted, clean up global params + if self.state == "deleted": + if not self.want or (self.have.get("as_number") == self.want.get("as_number")): + self._compare(want={}, have=self.have) + + elif self.state == "purged": + if not self.want or (self.have.get("as_number") == self.want.get("as_number")): + self.addcmd(self.have or {}, "as_number", True) + + else: + wantd = self.want + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(self.have, self.want) + + self._compare(want=wantd, have=self.have) + + def _compare(self, want, have, vrf=None): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Bgp_global network resource. + """ + begin = len(self.commands) + self.compare(parsers=self.parsers, want=want, have=have) + self._compare_confederation_peers(want, have) + self._compare_neighbors(want, have, vrf=vrf) + self._vrfs_compare(want=want, have=have) + + if len(self.commands) != begin or (not have and want): + self.commands.insert( + begin, + self._tmplt.render( + want or have, + "vrf" if "vrf" in (want.keys() or have.keys()) else "as_number", + False, + ), + ) + + def _compare_confederation_peers(self, want, have): + """Custom handling of confederation.peers option + + :params want: the want BGP dictionary + :params have: the have BGP dictionary + """ + w_cpeers = want.get("confederation", {}).get("peers", []) + h_cpeers = have.get("confederation", {}).get("peers", []) + + if set(w_cpeers) != set(h_cpeers): + if self.state in ["replaced", "deleted"]: + # if there are peers already configured + # we need to remove those before we pass + # the new ones otherwise the device appends + # them to the existing ones + if h_cpeers: + self.addcmd(have, "confederation.peers", True) + if w_cpeers: + self.addcmd(want, "confederation.peers", False) + + def _compare_neighbors(self, want, have, vrf=None): + """Custom handling of neighbors option + + :params want: the want BGP dictionary + :params have: the have BGP dictionary + """ + nbr_parsers = [ + "remote_as", + "bfd", + "bfd.multihop.interval", + "neighbor_affinity_group.group_id", + "bmp_activate_server", + "capability", + "description", + "disable_connected_check", + "dont_capability_negotiate", + "dscp", + "dynamic_capability", + "ebgp_multihop", + "graceful_shutdown", + "inherit.peer", + "inherit.peer_session", + "local_as", + "log_neighbor_changes", + "low_memory", + "password", + "peer_type", + "remove_private_as", + "shutdown", + "timers", + "transport", + "ttl_security", + "update_source", + ] + wnbrs = want.get("neighbors", {}) + hnbrs = have.get("neighbors", {}) + + # neighbors have separate contexts in NX-OS + for name, entry in iteritems(wnbrs): + begin = len(self.commands) + have_nbr = hnbrs.pop(name, {}) + + self.compare(parsers=nbr_parsers, want=entry, have=have_nbr) + self._compare_path_attribute(entry, have_nbr) + + if len(self.commands) != begin: + self.commands.insert(begin, self._tmplt.render(entry, "neighbor_address", False)) + + # cleanup remaining neighbors + # but do not negate it entirely + # instead remove only those attributes + # that this module manages + for name, entry in iteritems(hnbrs): + if self._has_af(vrf=vrf, neighbor=name): + self._module.fail_json( + msg="Neighbor {0} has address-family configurations. " + "Please use the nxos_bgp_neighbor_af module to remove those first.".format( + name, + ), + ) + else: + self.addcmd(entry, "neighbor_address", True) + + def _compare_path_attribute(self, want, have): + """Custom handling of neighbor path_attribute + option. + + :params want: the want neighbor dictionary + :params have: the have neighbor dictionary + """ + w_p_attr = want.get("path_attribute", {}) + h_p_attr = have.get("path_attribute", {}) + + for wkey, wentry in iteritems(w_p_attr): + if wentry != h_p_attr.pop(wkey, {}): + self.addcmd(wentry, "path_attribute", False) + + # remove remaining items in have for replaced + for hkey, hentry in iteritems(h_p_attr): + self.addcmd(hentry, "path_attribute", True) + + def _vrfs_compare(self, want, have): + """Custom handling of VRFs option + + :params want: the want BGP dictionary + :params have: the have BGP dictionary + """ + wvrfs = want.get("vrfs", {}) + hvrfs = have.get("vrfs", {}) + for name, entry in iteritems(wvrfs): + self._compare(want=entry, have=hvrfs.pop(name, {}), vrf=name) + # cleanup remaining VRFs + # but do not negate it entirely + # instead remove only those attributes + # that this module manages + for name, entry in iteritems(hvrfs): + if self._has_af(vrf=name): + self._module.fail_json( + msg="VRF {0} has address-family configurations. " + "Please use the nxos_bgp_af module to remove those first.".format(name), + ) + else: + self.addcmd(entry, "vrf", True) + + def _bgp_list_to_dict(self, entry): + """Convert list of items to dict of items + for efficient diff calculation. + + :params entry: data dictionary + """ + + def _build_key(x): + """Build primary key for path_attribute + option. + :params x: path_attribute dictionary + :returns: primary key as tuple + """ + key_1 = "start_{0}".format(x.get("range", {}).get("start", "")) + key_2 = "end_{0}".format(x.get("range", {}).get("end", "")) + key_3 = "type_{0}".format(x.get("type", "")) + key_4 = x["action"] + + return (key_1, key_2, key_3, key_4) + + if "neighbors" in entry: + for x in entry["neighbors"]: + if "path_attribute" in x: + x["path_attribute"] = { + _build_key(item): item for item in x.get("path_attribute", []) + } + + entry["neighbors"] = {x["neighbor_address"]: x for x in entry.get("neighbors", [])} + + if "vrfs" in entry: + entry["vrfs"] = {x["vrf"]: x for x in entry.get("vrfs", [])} + for _k, vrf in iteritems(entry["vrfs"]): + self._bgp_list_to_dict(vrf) + + def _get_config(self): + return self._connection.get("show running-config | section '^router bgp'") + + def _build_af_data(self): + """Build a dictionary with AF related information + from fetched BGP config. + _af_data = { + gbl_data = {'192.168.1.100', '192.168.1.101'}, + vrf_data = { + 'vrf_1': { + 'has_af': True, + 'nbrs': {'192.0.1.1', '192.8.1.1'} + }, + 'vrf_2': { + 'has_af': False, + 'nbrs': set() + } + } + } + """ + data = self._get_config().split("\n") + cur_nbr = None + cur_vrf = None + gbl_data = set() + vrf_data = {} + + for x in data: + if x.strip().startswith("vrf"): + cur_nbr = None + cur_vrf = x.split(" ")[-1] + vrf_data[cur_vrf] = {"nbrs": set(), "has_af": False} + + elif x.strip().startswith("neighbor"): + cur_nbr = x.split(" ")[-1] + + elif x.strip().startswith("address-family"): + if cur_nbr: + if cur_vrf: + vrf_data[cur_vrf]["nbrs"].add(cur_nbr) + else: + gbl_data.add(cur_nbr) + else: + if cur_vrf: + vrf_data[cur_vrf]["has_af"] = True + + self._af_data["global"] = gbl_data + self._af_data["vrf"] = vrf_data + + def _has_af(self, vrf=None, neighbor=None): + """Determine if the given vrf + neighbor + combination has AF configurations. + + :params vrf: vrf name + :params neighbor: neighbor name + :returns: bool + """ + has_af = False + + if self._af_data: + vrf_af_data = self._af_data.get("vrf", {}) + global_af_data = self._af_data.get("global", set()) + if vrf: + vrf_nbr_has_af = vrf_af_data.get(vrf, {}).get("nbrs", set()) + vrf_has_af = vrf_af_data.get(vrf, {}).get("has_af", False) + if neighbor and neighbor in vrf_nbr_has_af: + # we are inspecting neighbor within a VRF + # if the given neighbor has AF we return True + has_af = True + else: + # we are inspecting VRF as a whole + # if there is at least one neighbor + # with AF or VRF has AF itself return True + if vrf_nbr_has_af or vrf_has_af: + has_af = True + else: + # we are inspecting top level neighbors + # if the given neighbor has AF we return True + if neighbor and neighbor in global_af_data: + has_af = True + + return has_af diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/bgp_neighbor_address_family.py new file mode 100644 index 00000000..96902987 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/bgp_neighbor_address_family.py @@ -0,0 +1,232 @@ +# +# -*- 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) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_bgp_neighbor_address_family config file. +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 its desired end-state is +created. +""" +from copy import deepcopy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_neighbor_address_family import ( + Bgp_neighbor_address_familyTemplate, +) + + +class Bgp_neighbor_address_family(ResourceModule): + """ + The nxos_bgp_neighbor_address_family config class + """ + + def __init__(self, module): + super(Bgp_neighbor_address_family, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="bgp_neighbor_address_family", + tmplt=Bgp_neighbor_address_familyTemplate(), + ) + self.parsers = [ + "advertise_map.exist_map", + "advertise_map.non_exist_map", + "advertisement_interval", + "allowas_in", + "as_override", + "capability.additional_paths.receive", + "capability.additional_paths.send", + "default_originate", + "disable_peer_as_check", + "filter_list.inbound", + "filter_list.outbound", + "inherit", + "maximum_prefix", + "next_hop_self", + "next_hop_third_party", + "prefix_list.inbound", + "prefix_list.outbound", + "rewrite_evpn_rt_asn", + "route_map.inbound", + "route_map.outbound", + "route_reflector_client", + "send_community.extended", + "send_community.standard", + "soft_reconfiguration_inbound", + "soo", + "suppress_inactive", + "unsuppress_map", + "weight", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = deepcopy(self.want) + haved = deepcopy(self.have) + + for entry in wantd, haved: + self._bgp_list_to_dict(entry) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to elements to delete + if self.state == "deleted": + if wantd: + to_del = { + "neighbors": self._set_to_delete(haved, wantd), + "vrfs": {}, + } + + for k, hvrf in iteritems(haved.get("vrfs", {})): + wvrf = wantd.get("vrfs", {}).get(k, {}) + to_del["vrfs"][k] = { + "neighbors": self._set_to_delete(hvrf, wvrf), + "vrf": k, + } + haved.update(to_del) + + wantd = {} + + self._compare(want=wantd, have=haved) + + if self.commands: + self.commands.insert(0, "router bgp {as_number}".format(**haved or wantd)) + + def _compare(self, want, have, vrf=""): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Bgp_neighbor_address_family network resource. + """ + w_nbrs = want.get("neighbors", {}) + h_nbrs = have.get("neighbors", {}) + + if vrf: + begin_vrf = len(self.commands) + + for k, w_nbr in iteritems(w_nbrs): + begin = len(self.commands) + h_nbr = h_nbrs.pop(k, {}) + want_afs = w_nbr.get("address_family", {}) + have_afs = h_nbr.get("address_family", {}) + + for k, want_af in iteritems(want_afs): + begin_af = len(self.commands) + have_af = have_afs.pop(k, {}) + + # swap `both` and `set` for idempotence + if "send_community" in want_af: + if want_af["send_community"].get("both"): + want_af["send_community"] = { + "extended": True, + "standard": True, + } + elif want_af["send_community"].get("set"): + want_af["send_community"].update({"standard": True}) + + self.compare(parsers=self.parsers, want=want_af, have=have_af) + + if len(self.commands) != begin_af or (not have_af and want_af): + self.commands.insert( + begin_af, + self._tmplt.render(want_af, "address_family", False), + ) + + # remove remaining items in have for replaced + for k, have_af in iteritems(have_afs): + self.addcmd(have_af, "address_family", True) + + if len(self.commands) != begin: + self.commands.insert(begin, "neighbor {0}".format(w_nbr["neighbor_address"])) + + if self.state in ["overridden", "deleted"]: + for k, h_nbr in iteritems(h_nbrs): + begin = len(self.commands) + if not w_nbrs.pop(k, {}): + have_afs = h_nbr.get("address_family", {}) + for k, have_af in iteritems(have_afs): + self.addcmd(have_af, "address_family", True) + if len(self.commands) != begin: + self.commands.insert(begin, "neighbor {0}".format(h_nbr["neighbor_address"])) + + if vrf: + if len(self.commands) != begin_vrf: + self.commands.insert(begin_vrf, "vrf {0}".format(vrf)) + else: + self._vrfs_compare(want, have) + + def _vrfs_compare(self, want, have): + wvrfs = want.get("vrfs", {}) + hvrfs = have.get("vrfs", {}) + for k, wvrf in iteritems(wvrfs): + h_vrf = hvrfs.pop(k, {}) + self._compare(want=wvrf, have=h_vrf, vrf=k) + # remove remaining items in have + for k, h_vrf in iteritems(hvrfs): + self._compare(want={}, have=h_vrf, vrf=k) + + def _bgp_list_to_dict(self, data): + if "neighbors" in data: + for nbr in data["neighbors"]: + if "address_family" in nbr: + nbr["address_family"] = { + (x["afi"], x.get("safi")): x for x in nbr["address_family"] + } + data["neighbors"] = {x["neighbor_address"]: x for x in data["neighbors"]} + + if "vrfs" in data: + for vrf in data["vrfs"]: + self._bgp_list_to_dict(vrf) + data["vrfs"] = {x["vrf"]: x for x in data["vrfs"]} + + def _set_to_delete(self, haved, wantd): + neighbors = {} + h_nbrs = haved.get("neighbors", {}) + w_nbrs = wantd.get("neighbors", {}) + + for k, h_nbr in iteritems(h_nbrs): + w_nbr = w_nbrs.pop(k, {}) + if w_nbr: + neighbors[k] = h_nbr + afs_to_del = {} + h_addrs = h_nbr.get("address_family", {}) + w_addrs = w_nbr.get("address_family", {}) + for af, h_addr in iteritems(h_addrs): + if af in w_addrs: + afs_to_del[af] = h_addr + neighbors[k]["address_family"] = afs_to_del + + return neighbors diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/hostname.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/hostname.py new file mode 100644 index 00000000..42ed0694 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/hostname.py @@ -0,0 +1,75 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# 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 + +""" +The nxos_hostname config file. +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 its desired end-state is +created. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.hostname import ( + HostnameTemplate, +) + + +class Hostname(ResourceModule): + """ + The nxos_hostname config class + """ + + def __init__(self, module): + super(Hostname, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="hostname", + tmplt=HostnameTemplate(), + ) + self.parsers = ["hostname"] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + want = self.want + have = self.have + + if self.state == "deleted": + want = {} + + self._compare(want, have) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Bgp_global network resource. + """ + self.compare(parsers=self.parsers, want=want, have=have) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/hsrp_interfaces.py new file mode 100644 index 00000000..757505e7 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/hsrp_interfaces.py @@ -0,0 +1,286 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The nxos hsrp_interfaces class +This class creates a command set to bring the current device configuration +to a desired end-state. The command set is based on a comparison of the +current configuration (as dict) and the provided configuration (as dict). +""" +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 ( + dict_diff, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + flatten_dict, + normalize_interface, + search_obj_in_list, +) + + +class Hsrp_interfaces(ConfigBase): + """ + The nxos_hsrp_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["hsrp_interfaces"] + + def __init__(self, module): + super(Hsrp_interfaces, self).__init__(module) + + def get_hsrp_interfaces_facts(self, data=None): + """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, + data=data, + ) + hsrp_interfaces_facts = facts["ansible_network_resources"].get("hsrp_interfaces", []) + return hsrp_interfaces_facts + + def edit_config(self, commands): + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = [] + commands = [] + + if self.state in self.ACTION_STATES: + existing_hsrp_interfaces_facts = self.get_hsrp_interfaces_facts() + else: + existing_hsrp_interfaces_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_hsrp_interfaces_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_hsrp_interfaces_facts = self.get_hsrp_interfaces_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_hsrp_interfaces_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_hsrp_interfaces_facts + if result["changed"]: + result["after"] = changed_hsrp_interfaces_facts + + elif self.state == "gathered": + result["gathered"] = changed_hsrp_interfaces_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_hsrp_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 + """ + config = self._module.params["config"] + want = [] + if config: + for w in config: + w.update({"name": normalize_interface(w["name"])}) + want.append(w) + have = existing_hsrp_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"] + # check for 'config' keyword in play + if state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + + cmds = list() + if state == "overridden": + cmds.extend(self._state_overridden(want, have)) + elif state == "deleted": + cmds.extend(self._state_deleted(want, have)) + else: + for w in want: + if state in ["merged", "rendered"]: + cmds.extend(self._state_merged(flatten_dict(w), have)) + elif state == "replaced": + cmds.extend(self._state_replaced(flatten_dict(w), have)) + return cmds + + def _state_replaced(self, want, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + cmds = [] + obj_in_have = search_obj_in_list(want["name"], have, "name") + if obj_in_have: + diff = dict_diff(want, obj_in_have) + else: + diff = want + merged_cmds = self.set_commands(want, have) + if "name" not in diff: + diff["name"] = want["name"] + + replaced_cmds = [] + if obj_in_have: + replaced_cmds = self.del_attribs(diff) + if replaced_cmds or merged_cmds: + for cmd in set(replaced_cmds).intersection(set(merged_cmds)): + merged_cmds.remove(cmd) + cmds.extend(replaced_cmds) + cmds.extend(merged_cmds) + return cmds + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + cmds = [] + for h in have: + # Check existing states, set to default if not in want or different than want + h = flatten_dict(h) + obj_in_want = search_obj_in_list(h["name"], want, "name") + if obj_in_want: + # Let the 'want' loop handle all vals for this interface + continue + cmds.extend(self.del_attribs(h)) + for w in want: + # Update any want attrs if needed. The overridden state considers + # the play as the source of truth for the entire device, therefore + # set any unspecified attrs to their default state. + w = self.set_none_vals_to_defaults(flatten_dict(w)) + cmds.extend(self.set_commands(w, have)) + return cmds + + def _state_merged(self, want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(want, have) + + 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 + """ + if not (want or have): + return [] + cmds = [] + if want: + for w in want: + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + cmds.extend(self.del_attribs(obj_in_have)) + else: + for h in have: + cmds.extend(self.del_attribs(flatten_dict(h))) + return cmds + + def del_attribs(self, obj): + if not obj or len(obj.keys()) == 1: + return [] + cmds = [] + if "bfd" in obj: + cmds.append("no hsrp bfd") + if cmds: + cmds.insert(0, "interface " + obj["name"]) + return cmds + + def set_none_vals_to_defaults(self, want): + # Set dict None values to default states + if "bfd" in want and want["bfd"] is None: + want["bfd"] = "disable" + return want + + def diff_of_dicts(self, want, obj_in_have): + diff = set(want.items()) - set(obj_in_have.items()) + diff = dict(diff) + if diff and want["name"] == obj_in_have["name"]: + diff.update({"name": want["name"]}) + return diff + + def add_commands(self, want, obj_in_have): + if not want: + return [] + cmds = [] + if "bfd" in want and want["bfd"] is not None: + if want["bfd"] == "enable": + cmd = "hsrp bfd" + cmds.append(cmd) + elif want["bfd"] == "disable" and obj_in_have and obj_in_have.get("bfd") == "enable": + cmd = "no hsrp bfd" + cmds.append(cmd) + + if cmds: + cmds.insert(0, "interface " + want["name"]) + return cmds + + def set_commands(self, want, have): + cmds = [] + obj_in_have = search_obj_in_list(want["name"], have, "name") + if not obj_in_have: + cmds = self.add_commands(want, obj_in_have) + else: + diff = self.diff_of_dicts(want, obj_in_have) + cmds = self.add_commands(diff, obj_in_have) + return cmds diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/interfaces.py new file mode 100644 index 00000000..32c5f6fe --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/interfaces.py @@ -0,0 +1,492 @@ +# +# -*- 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 nxos_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 re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_diff, + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + default_intf_enabled, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + normalize_interface, + search_obj_in_list, +) + + +class Interfaces(ConfigBase): + """ + The nxos_interfaces class + """ + + gather_subset = ["min"] + + gather_network_resources = ["interfaces"] + + exclude_params = ["description", "mtu", "speed", "duplex"] + + def __init__(self, module): + super(Interfaces, self).__init__(module) + + def get_interfaces_facts(self, data=None): + """Get the 'facts' (the current configuration) + + :data: Mocked running-config data for state `parsed` + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + self.facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, + self.gather_network_resources, + data=data, + ) + interfaces_facts = self.facts["ansible_network_resources"].get("interfaces") + + return interfaces_facts + + def get_platform(self): + """Wrapper method for getting platform info + This method exists solely to allow the unit test framework to mock calls. + """ + return self.facts.get("ansible_net_platform", "") + + def get_system_defaults(self): + """Wrapper method for `_connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return self._connection.get("show running-config all | incl 'system default switchport'") + + def edit_config(self, commands): + """Wrapper method for `_connection.edit_config()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + commands = [] + warnings = [] + + if self.state in self.ACTION_STATES: + existing_interfaces_facts = self.get_interfaces_facts() + else: + existing_interfaces_facts = [] + + if self.state in self.ACTION_STATES: + self.intf_defs = self.render_interface_defaults( + self.get_system_defaults(), + existing_interfaces_facts, + ) + commands.extend(self.set_config(existing_interfaces_facts)) + + if self.state == "rendered": + # Hardcode the system defaults for "rendered" + # This can be made a configurable option in the future + self.intf_defs = { + "sysdefs": { + "L2_enabled": False, + "L3_enabled": False, + "mode": "layer3", + }, + } + commands.extend(self.set_config(existing_interfaces_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_interfaces_facts = self.get_interfaces_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_interfaces_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_interfaces_facts + if result["changed"]: + result["after"] = changed_interfaces_facts + + elif self.state == "gathered": + result["gathered"] = 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 + """ + config = self._module.params.get("config") + want = [] + if config: + for w in config: + w.update({"name": normalize_interface(w["name"])}) + want.append(remove_empties(w)) + have = deepcopy(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"] + if state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + + commands = list() + if state == "overridden": + commands.extend(self._state_overridden(want, have)) + elif state == "deleted": + commands.extend(self._state_deleted(want, have)) + elif state == "purged": + commands.extend(self._state_purged(want, have)) + else: + for w in want: + if state in ["merged", "rendered"]: + commands.extend(self._state_merged(w, have)) + elif state == "replaced": + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_replaced(self, w, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + name = w["name"] + obj_in_have = search_obj_in_list(name, have, "name") + if obj_in_have: + # If 'w' does not specify mode then intf may need to change to its + # default mode, however default mode may depend on sysdef. + if not w.get("mode") and re.search("Ethernet|port-channel", name): + sysdefs = self.intf_defs["sysdefs"] + sysdef_mode = sysdefs["mode"] + if obj_in_have.get("mode") != sysdef_mode: + w["mode"] = sysdef_mode + diff = dict_diff(w, obj_in_have) + else: + diff = w + + merged_commands = self.set_commands(w, have) + # merged_commands: + # - These commands are changes specified by the playbook. + # - merged_commands apply to both existing and new objects + # replaced_commands: + # - These are the unspecified commands, used to reset any params + # that are not already set to default states + # - replaced_commands should only be used on 'have' objects + # (interfaces that already exist) + if obj_in_have: + if "name" not in diff: + diff["name"] = name + wkeys = w.keys() + dkeys = diff.keys() + for k in wkeys: + if k in self.exclude_params and k in dkeys: + del diff[k] + replaced_commands = self.del_attribs(diff) + cmds = set(replaced_commands).intersection(set(merged_commands)) + for cmd in cmds: + merged_commands.remove(cmd) + commands.extend(replaced_commands) + + commands.extend(merged_commands) + return commands + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + # overridden is the same as replaced behavior except for the scope. + cmds = [] + existing_interfaces = [] + for h in have: + existing_interfaces.append(h["name"]) + obj_in_want = search_obj_in_list(h["name"], want, "name") + if obj_in_want: + if h != obj_in_want: + replaced_cmds = self._state_replaced(obj_in_want, [h]) + if replaced_cmds: + cmds.extend(replaced_cmds) + else: + cmds.extend(self.del_attribs(h)) + + for w in want: + if w["name"] not in existing_interfaces: + # This is an object that was excluded from the 'have' list + # because all of its params are currently set to default states + # -OR- it's a new object that does not exist on the device yet. + cmds.extend(self.add_commands(w)) + return cmds + + def _state_merged(self, w, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(w, have) + + 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 = [] + if want: + for w in want: + obj_in_have = search_obj_in_list(w["name"], have, "name") + commands.extend(self.del_attribs(obj_in_have)) + else: + if not have: + return commands + for h in have: + commands.extend(self.del_attribs(h)) + return commands + + def _state_purged(self, want, have): + """The command generator when state is purged + + :rtype: A list + :returns: the commands necessary to purge interfaces from running + configuration + """ + commands = [] + if want: + for w in want: + obj_in_have = search_obj_in_list(w["name"], have, "name") + if obj_in_have: + commands.append("no interface {0}".format(w["name"])) + return commands + + def default_enabled(self, want=None, have=None, action=""): + # 'enabled' default state depends on the interface type and L2 state. + # Note that the current default could change when changing L2/L3 modes. + if self.state == "rendered": + # For "rendered", we always assume that + # the default enabled state is False + return False + if want is None: + want = {} + if have is None: + have = {} + name = have.get("name") + if name is None: + return None + + sysdefs = self.intf_defs["sysdefs"] + sysdef_mode = sysdefs["mode"] + + # Get the default enabled state for this interface. This was collected + # during Facts gathering. + intf_def_enabled = self.intf_defs.get(name) + + have_mode = have.get("mode", sysdef_mode) + if action == "delete" and not want: + want_mode = sysdef_mode + else: + want_mode = want.get("mode", have_mode) + if ( + (want_mode and have_mode) is None + or (want_mode != have_mode) + or intf_def_enabled is None + ): + # L2-L3 is changing or this is a new virtual intf. Get new default. + intf_def_enabled = default_intf_enabled(name=name, sysdefs=sysdefs, mode=want_mode) + return intf_def_enabled + + def del_attribs(self, obj): + commands = [] + if not obj or len(obj.keys()) == 1: + return commands + # mode/switchport changes should occur before other changes + sysdef_mode = self.intf_defs["sysdefs"]["mode"] + if "mode" in obj and obj["mode"] != sysdef_mode: + no_cmd = "no " if sysdef_mode == "layer3" else "" + commands.append(no_cmd + "switchport") + if "description" in obj: + commands.append("no description") + if "speed" in obj: + commands.append("no speed") + if "duplex" in obj: + commands.append("no duplex") + if "enabled" in obj: + sysdef_enabled = self.default_enabled(have=obj, action="delete") + if obj["enabled"] is False and sysdef_enabled is True: + commands.append("no shutdown") + elif obj["enabled"] is True and sysdef_enabled is False: + commands.append("shutdown") + if "mtu" in obj: + commands.append("no mtu") + if "ip_forward" in obj and obj["ip_forward"] is True: + commands.append("no ip forward") + if ( + "fabric_forwarding_anycast_gateway" in obj + and obj["fabric_forwarding_anycast_gateway"] is True + ): + commands.append("no fabric forwarding mode anycast-gateway") + if commands: + commands.insert(0, "interface " + obj["name"]) + + return commands + + def diff_of_dicts(self, w, obj): + diff = set(w.items()) - set(obj.items()) + diff = dict(diff) + if diff and w["name"] == obj["name"]: + diff.update({"name": w["name"]}) + return diff + + def add_commands(self, d, obj_in_have=None): + commands = [] + if obj_in_have is None: + obj_in_have = {} + # mode/switchport changes should occur before other changes + if "mode" in d: + sysdef_mode = self.intf_defs["sysdefs"]["mode"] + have_mode = obj_in_have.get("mode", sysdef_mode) + want_mode = d["mode"] + if have_mode == "layer2": + if want_mode == "layer3": + commands.append("no switchport") + elif want_mode == "layer2": + commands.append("switchport") + if "description" in d: + commands.append("description " + d["description"]) + if "speed" in d: + commands.append("speed " + str(d["speed"])) + if "duplex" in d: + commands.append("duplex " + d["duplex"]) + if "enabled" in d: + have_enabled = obj_in_have.get("enabled", self.default_enabled(d, obj_in_have)) or False + if d["enabled"] is False and have_enabled is True: + commands.append("shutdown") + elif d["enabled"] is True and have_enabled is False: + commands.append("no shutdown") + if "mtu" in d: + commands.append("mtu " + str(d["mtu"])) + if "ip_forward" in d: + if d["ip_forward"] is True: + commands.append("ip forward") + else: + commands.append("no ip forward") + if "fabric_forwarding_anycast_gateway" in d: + if d["fabric_forwarding_anycast_gateway"] is True: + commands.append("fabric forwarding mode anycast-gateway") + else: + commands.append("no fabric forwarding mode anycast-gateway") + if commands or not obj_in_have: + commands.insert(0, "interface" + " " + d["name"]) + return commands + + def set_commands(self, w, have): + commands = [] + obj_in_have = search_obj_in_list(w["name"], have, "name") + if not obj_in_have: + commands = self.add_commands(w) + else: + diff = self.diff_of_dicts(w, obj_in_have) + commands = self.add_commands(diff, obj_in_have) + return commands + + def render_interface_defaults(self, config, intfs): + """Collect user-defined-default states for 'system default switchport' + configurations. These configurations determine default L2/L3 modes + and enabled/shutdown states. The default values for user-defined-default + configurations may be different for legacy platforms. + Notes: + - L3 enabled default state is False on N9K,N7K but True for N3K,N6K + - Changing L2-L3 modes may change the default enabled value. + - '(no) system default switchport shutdown' only applies to L2 interfaces. + Run through the gathered interfaces and tag their default enabled state. + """ + intf_defs = {} + L3_enabled = True if re.search("N[356]K", self.get_platform()) else False + intf_defs = { + "sysdefs": { + "mode": None, + "L2_enabled": None, + "L3_enabled": L3_enabled, + }, + } + pat = "(no )*system default switchport$" + m = re.search(pat, config, re.MULTILINE) + if m: + intf_defs["sysdefs"]["mode"] = "layer3" if "no " in m.groups() else "layer2" + + pat = "(no )*system default switchport shutdown$" + m = re.search(pat, config, re.MULTILINE) + if m: + intf_defs["sysdefs"]["L2_enabled"] = True if "no " in m.groups() else False + + for item in intfs: + intf_defs[item["name"]] = default_intf_enabled( + name=item["name"], + sysdefs=intf_defs["sysdefs"], + mode=item.get("mode"), + ) + + return intf_defs diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py new file mode 100644 index 00000000..e7d91498 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py @@ -0,0 +1,351 @@ +# +# -*- 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 nxos_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 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 ( + dict_diff, + remove_empties, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + flatten_dict, + normalize_interface, + search_obj_in_list, + vlan_list_to_range, + vlan_range_to_list, +) + + +class L2_interfaces(ConfigBase): + """ + The nxos_l2_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["l2_interfaces"] + + exclude_params = ["vlan", "allowed_vlans", "native_vlans"] + + def __init__(self, module): + super(L2_interfaces, self).__init__(module) + + def get_l2_interfaces_facts(self, data=None): + """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, + data=data, + ) + 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} + commands = [] + warnings = [] + + if self.state in self.ACTION_STATES: + existing_l2_interfaces_facts = self.get_l2_interfaces_facts() + else: + existing_l2_interfaces_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_l2_interfaces_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_l2_interfaces_facts = self.get_l2_interfaces_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_l2_interfaces_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_l2_interfaces_facts + if result["changed"]: + result["after"] = changed_l2_interfaces_facts + + elif self.state == "gathered": + result["gathered"] = 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 + """ + config = self._module.params.get("config") + want = [] + if config: + for w in config: + w.update({"name": normalize_interface(w["name"])}) + self.expand_trunk_allowed_vlans(w) + want.append(remove_empties(w)) + have = existing_l2_interfaces_facts + for h in have: + self.expand_trunk_allowed_vlans(h) + resp = self.set_state(want, have) or [] + self._reconstruct_commands(resp) + + return resp + + def expand_trunk_allowed_vlans(self, d): + if not d: + return None + if "trunk" in d and d["trunk"]: + if "allowed_vlans" in d["trunk"]: + allowed_vlans = vlan_range_to_list(d["trunk"]["allowed_vlans"]) + vlans_list = [str(l) for l in sorted(allowed_vlans)] + d["trunk"]["allowed_vlans"] = ",".join(vlans_list) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params["state"] + if state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + + commands = list() + if state == "overridden": + commands.extend(self._state_overridden(want, have)) + elif state == "deleted": + commands.extend(self._state_deleted(want, have)) + else: + for w in want: + if state in ["merged", "rendered"]: + commands.extend(self._state_merged(flatten_dict(w), have)) + elif state == "replaced": + commands.extend(self._state_replaced(flatten_dict(w), have)) + return commands + + def _state_replaced(self, w, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + if obj_in_have: + diff = dict_diff(w, obj_in_have) + else: + diff = w + merged_commands = self.set_commands(w, have, True) + if "name" not in diff: + diff["name"] = w["name"] + + dkeys = diff.keys() + for k in w.copy(): + if k in self.exclude_params and k in dkeys: + del diff[k] + replaced_commands = self.del_attribs(diff) + + if merged_commands or replaced_commands: + cmds = set(replaced_commands).intersection(set(merged_commands)) + for cmd in cmds: + merged_commands.remove(cmd) + commands.extend(replaced_commands) + commands.extend(merged_commands) + return commands + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + for h in have: + h = flatten_dict(h) + obj_in_want = flatten_dict(search_obj_in_list(h["name"], want, "name")) + if h == obj_in_want: + continue + for w in want: + w = flatten_dict(w) + if h["name"] == w["name"]: + wkeys = w.keys() + hkeys = h.keys() + for k in wkeys: + if k in self.exclude_params and k in hkeys: + del h[k] + commands.extend(self.del_attribs(h)) + for w in want: + commands.extend(self.set_commands(flatten_dict(w), have, True)) + return commands + + def _state_merged(self, w, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(w, have) + + 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 = [] + if want: + for w in want: + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + commands.extend(self.del_attribs(obj_in_have)) + else: + if not have: + return commands + for h in have: + commands.extend(self.del_attribs(flatten_dict(h))) + return commands + + def del_attribs(self, obj): + commands = [] + if not obj or len(obj.keys()) == 1: + return commands + + cmd = "no switchport " + if "vlan" in obj: + commands.append(cmd + "access vlan") + if "mode" in obj: + commands.append(cmd + "mode") + if "allowed_vlans" in obj: + commands.append(cmd + "trunk allowed vlan") + if "native_vlan" in obj: + commands.append(cmd + "trunk native vlan") + if commands: + commands.insert(0, "interface " + obj["name"]) + return commands + + def diff_of_dicts(self, w, obj): + diff = set(w.items()) - set(obj.items()) + diff = dict(diff) + if diff and w["name"] == obj["name"]: + diff.update({"name": w["name"]}) + return diff + + def add_commands(self, d, vlan_exists=False): + commands = [] + if not d: + return commands + + cmd = "switchport " + if "mode" in d: + commands.append(cmd + "mode {0}".format(d["mode"])) + if "vlan" in d: + commands.append(cmd + "access vlan " + str(d["vlan"])) + if "allowed_vlans" in d: + if vlan_exists: + commands.append(cmd + "trunk allowed vlan add " + str(d["allowed_vlans"])) + else: + commands.append(cmd + "trunk allowed vlan " + str(d["allowed_vlans"])) + if "native_vlan" in d: + commands.append(cmd + "trunk native vlan " + str(d["native_vlan"])) + if commands: + commands.insert(0, "interface " + d["name"]) + return commands + + def set_commands(self, w, have, replace=False): + commands = [] + + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + if not obj_in_have: + commands = self.add_commands(w) + else: + diff = self.diff_of_dicts(w, obj_in_have) + if diff and not replace: + if "mode" in diff.keys() and diff["mode"]: + commands = self.add_commands(diff) + if "allowed_vlans" in diff.keys() and diff["allowed_vlans"]: + vlan_tobe_added = diff["allowed_vlans"].split(",") + vlan_list = vlan_tobe_added[:] + if obj_in_have.get("allowed_vlans"): + have_vlans = obj_in_have["allowed_vlans"].split(",") + else: + have_vlans = [] + for w_vlans in vlan_list: + if w_vlans in have_vlans: + vlan_tobe_added.pop(vlan_tobe_added.index(w_vlans)) + if vlan_tobe_added: + diff.update({"allowed_vlans": ",".join(vlan_tobe_added)}) + if have_vlans: + commands = self.add_commands(diff, True) + else: + commands = self.add_commands(diff) + return commands + commands = self.add_commands(diff) + return commands + + def _reconstruct_commands(self, cmds): + for idx, cmd in enumerate(cmds): + match = re.search( + r"^(?P<cmd>(no\s)?switchport trunk allowed vlan(\sadd)?)\s(?P<vlans>.+)", + cmd, + ) + if match: + data = match.groupdict() + unparsed = vlan_list_to_range(data["vlans"].split(",")) + cmds[idx] = data["cmd"] + " " + unparsed diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/l3_interfaces.py new file mode 100644 index 00000000..55ac2266 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/l3_interfaces.py @@ -0,0 +1,545 @@ +# +# -*- 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 nxos_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 + +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + normalize_interface, + search_obj_in_list, +) + + +class L3_interfaces(ConfigBase): + """ + The nxos_l3_interfaces class + """ + + gather_subset = ["min"] + + gather_network_resources = ["l3_interfaces"] + + exclude_params = [] + + def __init__(self, module): + super(L3_interfaces, self).__init__(module) + + def get_l3_interfaces_facts(self, data=None): + """Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + if self.state not in self.ACTION_STATES: + self.gather_subset = ["!all", "!min"] + + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, + self.gather_network_resources, + data=data, + ) + + l3_interfaces_facts = facts["ansible_network_resources"].get("l3_interfaces") + self.platform = facts.get("ansible_net_platform", "") + + return l3_interfaces_facts + + def edit_config(self, commands): + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + commands = [] + warnings = [] + + if self.state in self.ACTION_STATES: + existing_l3_interfaces_facts = self.get_l3_interfaces_facts() + else: + existing_l3_interfaces_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_l3_interfaces_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_l3_interfaces_facts = self.get_l3_interfaces_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_l3_interfaces_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_l3_interfaces_facts + if result["changed"]: + result["after"] = changed_l3_interfaces_facts + + elif self.state == "gathered": + result["gathered"] = 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 + """ + config = self._module.params.get("config") + want = [] + if config: + for w in config: + w.update({"name": normalize_interface(w["name"])}) + want.append(remove_empties(w)) + have = deepcopy(existing_l3_interfaces_facts) + self.init_check_existing(have) + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params["state"] + if state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + + commands = [] + if state == "overridden": + commands.extend(self._state_overridden(want, have)) + elif state == "deleted": + commands.extend(self._state_deleted(want, have)) + else: + for w in want: + if state in ["merged", "rendered"]: + commands.extend(self._state_merged(w, have)) + elif state == "replaced": + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_replaced(self, want, have): + """The command generator when state is replaced + Scope is limited to interface objects defined in the playbook. + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + cmds = [] + name = want["name"] + obj_in_have = search_obj_in_list(want["name"], have, "name") + + have_v4 = obj_in_have.pop("ipv4", []) if obj_in_have else [] + have_v6 = obj_in_have.pop("ipv6", []) if obj_in_have else [] + + # Process lists of dicts separately + v4_cmds = self._v4_cmds(want.pop("ipv4", []), have_v4, state="replaced") + v6_cmds = self._v6_cmds(want.pop("ipv6", []), have_v6, state="replaced") + + # Process remaining attrs + if obj_in_have: + # Find 'want' changes first + diff = self.diff_of_dicts(want, obj_in_have) + rmv = {"name": name} + haves_not_in_want = set(obj_in_have.keys()) - set(want.keys()) - set(diff.keys()) + for i in haves_not_in_want: + rmv[i] = obj_in_have[i] + cmds.extend(self.generate_delete_commands(rmv)) + else: + diff = want + + cmds.extend(self.add_commands(diff, name=name)) + cmds.extend(v4_cmds) + cmds.extend(v6_cmds) + self.cmd_order_fixup(cmds, name) + return cmds + + def _state_overridden(self, want, have): + """The command generator when state is overridden + Scope includes all interface objects on the device. + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + # overridden behavior is the same as replaced except for scope. + cmds = [] + for i in have: + obj_in_want = search_obj_in_list(i["name"], want, "name") + if obj_in_want: + if i != obj_in_want: + v4_cmds = self._v4_cmds( + obj_in_want.pop("ipv4", []), + i.pop("ipv4", []), + state="overridden", + ) + replaced_cmds = self._state_replaced(obj_in_want, [i]) + replaced_cmds.extend(v4_cmds) + self.cmd_order_fixup(replaced_cmds, obj_in_want["name"]) + cmds.extend(replaced_cmds) + else: + deleted_cmds = self.generate_delete_commands(i) + self.cmd_order_fixup(deleted_cmds, i["name"]) + cmds.extend(deleted_cmds) + + for i in want: + if [item for item in have if i["name"] == item["name"]]: + continue + cmds.extend(self.add_commands(i, name=i["name"])) + + return cmds + + def _state_merged(self, w, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(w, have) + + def _v4_cmds(self, want, have, state=None): + """Helper method for processing ipv4 changes. + This is needed to handle primary/secondary address changes, which require a specific sequence when changing. + """ + # The ip address cli does not allow removing primary addresses while + # secondaries are present, but it does allow changing a primary to a + # new address as long as the address is not a current secondary. + # Be aware of scenarios where a secondary is taking over + # the role of the primary, which must be changed in sequence. + # In general, primaries/secondaries should change in this order: + # Step 1. Remove secondaries that are being changed or removed + # Step 2. Change the primary if needed + # Step 3. Merge secondaries + + # Normalize inputs (add tag key if not present) + for i in want: + i["tag"] = i.get("tag") + for i in have: + i["tag"] = i.get("tag") + + merged = True if state == "merged" else False + replaced = True if state == "replaced" else False + overridden = True if state == "overridden" else False + + # Create secondary and primary wants/haves + sec_w = [i for i in want if i.get("secondary")] + sec_h = [i for i in have if i.get("secondary")] + pri_w = [i for i in want if not i.get("secondary")] + pri_h = [i for i in have if not i.get("secondary")] + pri_w = pri_w[0] if pri_w else {} + pri_h = pri_h[0] if pri_h else {} + cmds = [] + + # Remove all addrs when no primary is specified in want (pri_w) + if pri_h and not pri_w and (replaced or overridden): + cmds.append("no ip address") + return cmds + + # 1. Determine which secondaries are changing and remove them. Need a have/want + # diff instead of want/have because a have sec addr may be changing to a pri. + sec_to_rmv = [] + sec_diff = self.diff_list_of_dicts(sec_h, sec_w) + for i in sec_diff: + if overridden or [w for w in sec_w if w["address"] == i["address"]]: + sec_to_rmv.append(i["address"]) + + # Check if new primary is currently a secondary + if pri_w and [h for h in sec_h if h["address"] == pri_w["address"]]: + if not overridden: + sec_to_rmv.append(pri_w["address"]) + + # Remove the changing secondaries + cmds.extend(["no ip address %s secondary" % i for i in sec_to_rmv]) + + # 2. change primary + if pri_w: + diff = dict(set(pri_w.items()) - set(pri_h.items())) + if diff: + addr = diff.get("address") or pri_w.get("address") + cmd = "ip address %s" % addr + tag = diff.get("tag") + cmd += " tag %s" % tag if tag else "" + cmds.append(cmd) + + # 3. process remaining secondaries last + sec_w_to_chg = self.diff_list_of_dicts(sec_w, sec_h) + for i in sec_w_to_chg: + cmd = "ip address %s secondary" % i["address"] + cmd += " tag %s" % i["tag"] if i["tag"] else "" + cmds.append(cmd) + + return cmds + + def _v6_cmds(self, want, have, state=""): + """Helper method for processing ipv6 changes. + This is needed to avoid unnecessary churn on the device when removing or changing multiple addresses. + """ + # Normalize inputs (add tag key if not present) + for i in want: + i["tag"] = i.get("tag") + for i in have: + i["tag"] = i.get("tag") + + cmds = [] + # items to remove (items in 'have' only) + if state == "replaced": + for i in self.diff_list_of_dicts(have, want): + want_addr = [w for w in want if w["address"] == i["address"]] + if not want_addr: + cmds.append("no ipv6 address %s" % i["address"]) + elif i["tag"] and not want_addr[0]["tag"]: + # Must remove entire cli when removing tag + cmds.append("no ipv6 address %s" % i["address"]) + + # items to merge/add + for i in self.diff_list_of_dicts(want, have): + addr = i["address"] + tag = i["tag"] + if not tag and state == "merged": + # When want is IP-no-tag and have is IP+tag it will show up in diff, + # but for merged nothing has changed, so ignore it for idempotence. + have_addr = [h for h in have if h["address"] == addr] + if have_addr and have_addr[0].get("tag"): + continue + cmd = "ipv6 address %s" % i["address"] + cmd += " tag %s" % tag if tag else "" + cmds.append(cmd) + + return cmds + + 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 = [] + if want: + for w in want: + obj_in_have = search_obj_in_list(w["name"], have, "name") + commands.extend(self.del_all_attribs(obj_in_have)) + else: + if not have: + return commands + for h in have: + commands.extend(self.del_all_attribs(h)) + return commands + + def del_all_attribs(self, obj): + commands = [] + if not obj or len(obj.keys()) == 1: + return commands + commands = self.generate_delete_commands(obj) + self.cmd_order_fixup(commands, obj["name"]) + return commands + + def generate_delete_commands(self, obj): + """Generate CLI commands to remove non-default settings. + obj: dict of attrs to remove + """ + commands = [] + name = obj.get("name") + if "dot1q" in obj: + commands.append("no encapsulation dot1q") + if "redirects" in obj: + if not self.check_existing(name, "has_secondary") or re.match( + "N[35679]", + self.platform, + ): + # device auto-enables redirects when secondaries are removed; + # auto-enable may fail on legacy platforms so always do explicit enable + commands.append("ip redirects") + if "ipv6_redirects" in obj: + if not self.check_existing(name, "has_secondary") or re.match( + "N[35679]", + self.platform, + ): + # device auto-enables redirects when secondaries are removed; + # auto-enable may fail on legacy platforms so always do explicit enable + commands.append("ipv6 redirects") + if "unreachables" in obj: + commands.append("no ip unreachables") + if "ipv4" in obj: + commands.append("no ip address") + if "ipv6" in obj: + commands.append("no ipv6 address") + if "evpn_multisite_tracking" in obj: + have = self.existing_facts.get(name, {}) + if have.get("evpn_multisite_tracking", False) is not False: + cmd = "no evpn multisite %s" % have.get("evpn_multisite_tracking") + commands.append(cmd) + return commands + + def init_check_existing(self, have): + """Creates a class var dict for easier access to existing states""" + self.existing_facts = dict() + have_copy = deepcopy(have) + for intf in have_copy: + name = intf["name"] + self.existing_facts[name] = intf + # Check for presence of secondaries; used for ip redirects logic + if [i for i in intf.get("ipv4", []) if i.get("secondary")]: + self.existing_facts[name]["has_secondary"] = True + + def check_existing(self, name, query): + """Helper method to lookup existing states on an interface. + This is needed for attribute changes that have additional dependencies; + e.g. 'ip redirects' may auto-enable when all secondary ip addrs are removed. + """ + if name: + have = self.existing_facts.get(name, {}) + if "has_secondary" in query: + return have.get("has_secondary", False) + if "redirects" in query: + return have.get("redirects", True) + if "unreachables" in query: + return have.get("unreachables", False) + return None + + def diff_of_dicts(self, w, obj): + diff = set(w.items()) - set(obj.items()) + diff = dict(diff) + if diff and w["name"] == obj["name"]: + diff.update({"name": w["name"]}) + return diff + + def diff_list_of_dicts(self, w, h): + diff = [] + set_w = set(tuple(sorted(d.items())) for d in w) if w else set() + set_h = set(tuple(sorted(d.items())) for d in h) if h else set() + difference = set_w.difference(set_h) + for element in difference: + diff.append(dict((x, y) for x, y in element)) + return diff + + def add_commands(self, diff, name=""): + commands = [] + if not diff: + return commands + if "dot1q" in diff: + commands.append("encapsulation dot1q " + str(diff["dot1q"])) + if "redirects" in diff: + # Note: device will auto-disable redirects when secondaries are present + if diff["redirects"] != self.check_existing(name, "redirects"): + no_cmd = "no " if diff["redirects"] is False else "" + commands.append(no_cmd + "ip redirects") + self.cmd_order_fixup(commands, name) + if "ipv6_redirects" in diff: + # Note: device will auto-disable redirects when secondaries are present + if diff["ipv6_redirects"] != self.check_existing(name, "ipv6_redirects"): + no_cmd = "no " if diff["ipv6_redirects"] is False else "" + commands.append(no_cmd + "ipv6 redirects") + self.cmd_order_fixup(commands, name) + if "unreachables" in diff: + if diff["unreachables"] != self.check_existing(name, "unreachables"): + no_cmd = "no " if diff["unreachables"] is False else "" + commands.append(no_cmd + "ip unreachables") + if "evpn_multisite_tracking" in diff: + commands.append("evpn multisite " + str(diff["evpn_multisite_tracking"])) + if "ipv4" in diff: + commands.extend(self.generate_afi_commands(diff["ipv4"])) + if "ipv6" in diff: + commands.extend(self.generate_afi_commands(diff["ipv6"])) + self.cmd_order_fixup(commands, name) + + return commands + + def generate_afi_commands(self, diff): + cmds = [] + for i in diff: + cmd = "ipv6 address " if re.search("::", i["address"]) else "ip address " + cmd += i["address"] + if i.get("secondary"): + cmd += " secondary" + if i.get("tag"): + cmd += " tag " + str(i["tag"]) + cmds.append(cmd) + return cmds + + def set_commands(self, w, have): + commands = [] + name = w["name"] + obj_in_have = search_obj_in_list(name, have, "name") + if not obj_in_have: + commands = self.add_commands(w, name=name) + else: + # lists of dicts must be processed separately from non-list attrs + v4_cmds = self._v4_cmds(w.pop("ipv4", []), obj_in_have.pop("ipv4", []), state="merged") + v6_cmds = self._v6_cmds(w.pop("ipv6", []), obj_in_have.pop("ipv6", []), state="merged") + + # diff remaining attrs + diff = self.diff_of_dicts(w, obj_in_have) + commands = self.add_commands(diff, name=name) + commands.extend(v4_cmds) + commands.extend(v6_cmds) + + self.cmd_order_fixup(commands, name) + return commands + + def cmd_order_fixup(self, cmds, name): + """Inserts 'interface <name>' config at the beginning of populated command list; reorders dependent commands that must process after others.""" + if cmds: + if name and not [item for item in cmds if item.startswith("interface")]: + cmds.insert(0, "interface " + name) + + redirects = [item for item in cmds if re.match("(no )*ip(v6)* redirects", item)] + if redirects: + # redirects should occur after ipv4 commands, just move to end of list + redirects = redirects.pop() + cmds.remove(redirects) + cmds.append(redirects) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/lacp.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/lacp.py new file mode 100644 index 00000000..4d334bad --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/lacp.py @@ -0,0 +1,234 @@ +# +# -*- 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 nxos_lacp 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 ( + dict_diff, + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts + + +class Lacp(ConfigBase): + """ + The nxos_lacp class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lacp"] + + exclude_params = ["priority", "mac"] + + def __init__(self, module): + super(Lacp, self).__init__(module) + + def get_lacp_facts(self, data=None): + """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, + data=data, + ) + lacp_facts = facts["ansible_network_resources"].get("lacp", {}) + + return lacp_facts + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + commands = list() + warnings = list() + + if self.state in self.ACTION_STATES: + existing_lacp_facts = self.get_lacp_facts() + else: + existing_lacp_facts = {} + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_lacp_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_lacp_facts = self.get_lacp_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_lacp_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_lacp_facts + if result["changed"]: + result["after"] = changed_lacp_facts + + elif self.state == "gathered": + result["gathered"] = changed_lacp_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_lacp_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(self._module.params["config"]) + have = existing_lacp_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params["state"] + if state in ("merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + + commands = list() + + if state == "deleted": + commands.extend(self._state_deleted(want, have)) + elif state in ["merged", "rendered"]: + commands.extend(self._state_merged(want, have)) + elif state == "replaced": + commands.extend(self._state_replaced(want, have)) + return commands + + def _state_replaced(self, want, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + diff = dict_diff(want, have) + wkeys = want.keys() + dkeys = diff.keys() + for k in wkeys: + if k in self.exclude_params and k in dkeys: + del diff[k] + deleted_commands = self.del_all(diff) + merged_commands = self._state_merged(want, have) + + commands.extend(deleted_commands) + if merged_commands: + commands.extend(merged_commands) + + return commands + + def _state_merged(self, want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(want, have) + + 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 = [] + if not have: + return commands + commands.extend(self.del_all(have)) + return commands + + def get_diff(self, comparable, base): + diff = {} + if not base: + diff = comparable + else: + diff = dict_diff(base, comparable) + return diff + + def del_all(self, diff): + commands = [] + base = "no lacp system-" + diff = diff.get("system") + if diff: + if "priority" in diff: + commands.append(base + "priority") + if "mac" in diff: + commands.append(base + "mac") + return commands + + def add_commands(self, diff): + commands = [] + base = "lacp system-" + diff = diff.get("system") + if diff and "priority" in diff: + cmd = base + "priority" + " " + str(diff["priority"]) + commands.append(cmd) + if diff and "mac" in diff: + cmd = "" + if "address" in diff["mac"]: + cmd += base + "mac" + " " + diff["mac"]["address"] + if "role" in diff["mac"]: + cmd += " " + "role" + " " + diff["mac"]["role"] + if cmd: + commands.append(cmd) + + return commands + + def set_commands(self, want, have): + if not want: + return [] + diff = self.get_diff(want, have) + return self.add_commands(diff) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000..eaad0763 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,323 @@ +# +# -*- 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 nxos_lacp_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 ( + dict_diff, + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + flatten_dict, + get_interface_type, + normalize_interface, + search_obj_in_list, +) + + +class Lacp_interfaces(ConfigBase): + """ + The nxos_lacp_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lacp_interfaces"] + + exclude_params = ["port_priority", "rate", "min", "max"] + + def __init__(self, module): + super(Lacp_interfaces, self).__init__(module) + + def get_lacp_interfaces_facts(self, data=None): + """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, + data=data, + ) + lacp_interfaces_facts = facts["ansible_network_resources"].get("lacp_interfaces") + if not lacp_interfaces_facts: + return [] + return lacp_interfaces_facts + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + commands = list() + warnings = list() + + if self.state in self.ACTION_STATES: + existing_lacp_interfaces_facts = self.get_lacp_interfaces_facts() + else: + existing_lacp_interfaces_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_lacp_interfaces_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_lacp_interfaces_facts = self.get_lacp_interfaces_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_lacp_interfaces_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_lacp_interfaces_facts + if result["changed"]: + result["after"] = changed_lacp_interfaces_facts + + elif self.state == "gathered": + result["gathered"] = changed_lacp_interfaces_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_lacp_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 + """ + config = self._module.params.get("config") + want = [] + if config: + for w in config: + if get_interface_type(w["name"]) not in ( + "portchannel", + "ethernet", + ): + self._module.fail_json( + msg="This module works with either portchannel or ethernet", + ) + w.update({"name": normalize_interface(w["name"])}) + want.append(remove_empties(w)) + have = existing_lacp_interfaces_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params["state"] + if state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + commands = list() + + if state == "overridden": + commands.extend(self._state_overridden(want, have)) + elif state == "deleted": + commands.extend(self._state_deleted(want, have)) + else: + for w in want: + if state in ["merged", "rendered"]: + commands.extend(self._state_merged(flatten_dict(w), have)) + elif state == "replaced": + commands.extend(self._state_replaced(flatten_dict(w), have)) + return commands + + def _state_replaced(self, w, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + diff = dict_diff(w, obj_in_have) + merged_commands = self.set_commands(w, have) + if "name" not in diff: + diff["name"] = w["name"] + wkeys = w.keys() + dkeys = diff.keys() + for k in wkeys: + if k in self.exclude_params and k in dkeys: + del diff[k] + replaced_commands = self.del_attribs(diff) + + if merged_commands: + cmds = set(replaced_commands).intersection(set(merged_commands)) + for cmd in cmds: + merged_commands.remove(cmd) + commands.extend(replaced_commands) + commands.extend(merged_commands) + return commands + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + for h in have: + h = flatten_dict(h) + obj_in_want = flatten_dict(search_obj_in_list(h["name"], want, "name")) + if h == obj_in_want: + continue + for w in want: + w = flatten_dict(w) + if h["name"] == w["name"]: + wkeys = w.keys() + hkeys = h.keys() + for k in wkeys: + if k in self.exclude_params and k in hkeys: + del h[k] + commands.extend(self.del_attribs(h)) + for w in want: + commands.extend(self.set_commands(flatten_dict(w), have)) + return commands + + def _state_merged(self, w, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(w, have) + + 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 = [] + if want: + for w in want: + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + commands.extend(self.del_attribs(obj_in_have)) + else: + if not have: + return commands + for h in have: + commands.extend(self.del_attribs(flatten_dict(h))) + return commands + + def del_attribs(self, obj): + commands = [] + if not obj or len(obj.keys()) == 1: + return commands + commands.append("interface " + obj["name"]) + if "graceful" in obj: + commands.append("lacp graceful-convergence") + if "vpc" in obj: + commands.append("no lacp vpn-convergence") + if "suspend_individual" in obj: + commands.append("lacp suspend_individual") + if "mode" in obj: + commands.append("no lacp mode " + obj["mode"]) + if "max" in obj: + commands.append("no lacp max-bundle") + if "min" in obj: + commands.append("no lacp min-links") + if "port_priority" in obj: + commands.append("no lacp port-priority") + if "rate" in obj: + commands.append("no lacp rate") + return commands + + def diff_of_dicts(self, w, obj): + diff = set(w.items()) - set(obj.items()) + diff = dict(diff) + if diff and w["name"] == obj["name"]: + diff.update({"name": w["name"]}) + return diff + + def add_commands(self, d): + commands = [] + if not d: + return commands + commands.append("interface" + " " + d["name"]) + + if "port_priority" in d: + commands.append("lacp port-priority " + str(d["port_priority"])) + if "rate" in d: + commands.append("lacp rate " + str(d["rate"])) + if "min" in d: + commands.append("lacp min-links " + str(d["min"])) + if "max" in d: + commands.append("lacp max-bundle " + str(d["max"])) + if "mode" in d: + commands.append("lacp mode " + d["mode"]) + if "suspend_individual" in d: + if d["suspend_individual"] is True: + commands.append("lacp suspend-individual") + else: + commands.append("no lacp suspend-individual") + if "graceful" in d: + if d["graceful"] is True: + commands.append("lacp graceful-convergence") + else: + commands.append("no lacp graceful-convergence") + if "vpc" in d: + if d["vpc"] is True: + commands.append("lacp vpc-convergence") + else: + commands.append("no lacp vpc-convergence") + return commands + + def set_commands(self, w, have): + commands = [] + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + if not obj_in_have: + commands = self.add_commands(w) + else: + diff = self.diff_of_dicts(w, obj_in_have) + commands = self.add_commands(diff) + return commands diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000..495244b4 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.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 nxos_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 + +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 ( + dict_diff, + remove_empties, + search_obj_in_list, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + normalize_interface, +) + + +class Lag_interfaces(ConfigBase): + """ + The nxos_lag_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lag_interfaces"] + + def __init__(self, module): + super(Lag_interfaces, self).__init__(module) + + def get_lag_interfaces_facts(self, data=None): + """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, + data=data, + ) + 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} + commands = list() + warnings = list() + + if self.state in self.ACTION_STATES: + existing_lag_interfaces_facts = self.get_lag_interfaces_facts() + else: + existing_lag_interfaces_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_lag_interfaces_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + resp = self._connection.edit_config(commands) + if "response" in resp: + for item in resp["response"]: + if item: + err_str = item + if err_str.lower().startswith("cannot add"): + self._module.fail_json(msg=err_str) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_lag_interfaces_facts = self.get_lag_interfaces_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_lag_interfaces_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_lag_interfaces_facts + if result["changed"]: + result["after"] = changed_lag_interfaces_facts + + elif self.state == "gathered": + result["gathered"] = 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.get("config") + if want: + for w in want: + w.update(remove_empties(w)) + if "members" in w and w["members"]: + for item in w["members"]: + item.update({"member": normalize_interface(item["member"])}) + 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 + """ + state = self._module.params["state"] + if state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + commands = list() + + if state == "overridden": + commands.extend(self._state_overridden(want, have)) + elif state == "deleted": + commands.extend(self._state_deleted(want, have)) + else: + for w in want: + if state in ["merged", "rendered"]: + commands.extend(self._state_merged(w, have)) + if state == "replaced": + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_replaced(self, w, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + merged_commands = self.set_commands(w, have) + replaced_commands = self.del_intf_commands(w, have) + if merged_commands: + commands.extend(replaced_commands) + commands.extend(merged_commands) + return commands + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + for h in have: + obj_in_want = search_obj_in_list(h["name"], want, "name") + if obj_in_want: + diff = self.diff_list_of_dicts(h.get("members", []), obj_in_want["members"]) + if not diff: + continue + commands.extend(self.del_all_commands(h)) + for w in want: + commands.extend(self.set_commands(w, have)) + return commands + + def _state_merged(self, w, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(w, have) + + 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 = [] + if want: + for w in want: + obj_in_have = search_obj_in_list(w["name"], have, "name") + commands.extend(self.del_all_commands(obj_in_have)) + else: + if not have: + return commands + for h in have: + commands.extend(self.del_all_commands(h)) + return commands + + def diff_list_of_dicts(self, want, have): + if not want: + want = [] + + if not have: + have = [] + + diff = [] + for w_item in want: + h_item = search_obj_in_list(w_item["member"], have, key="member") or {} + delta = dict_diff(h_item, w_item) + if delta: + if h_item: + if ( + "mode" in delta.keys() + and delta["mode"] == "on" + and "mode" not in h_item.keys() + ): + # mode = on will not be displayed in running-config + continue + if "member" not in delta.keys(): + delta["member"] = w_item["member"] + diff.append(delta) + + return diff + + def intersect_list_of_dicts(self, w, h): + intersect = [] + wmem = [] + hmem = [] + for d in w: + wmem.append({"member": d["member"]}) + for d in h: + hmem.append({"member": d["member"]}) + set_w = set(tuple(sorted(d.items())) for d in wmem) + set_h = set(tuple(sorted(d.items())) for d in hmem) + intersection = set_w.intersection(set_h) + for element in intersection: + intersect.append(dict((x, y) for x, y in element)) + return intersect + + def add_commands(self, diff, name): + commands = [] + name = name.strip("port-channel") + for d in diff: + commands.append("interface" + " " + d["member"]) + cmd = "" + group_cmd = "channel-group {0}".format(name) + if d.get("force"): + cmd = group_cmd + " force " + if "mode" in d: + if cmd: + cmd = cmd + " mode " + d["mode"] + else: + cmd = group_cmd + " mode " + d["mode"] + if not cmd: + cmd = group_cmd + commands.append(cmd) + return commands + + def set_commands(self, w, have): + commands = [] + obj_in_have = search_obj_in_list(w["name"], have, "name") + if not obj_in_have: + commands = self.add_commands(w["members"], w["name"]) + else: + if "members" not in obj_in_have: + obj_in_have["members"] = None + diff = self.diff_list_of_dicts(w["members"], obj_in_have["members"]) + commands = self.add_commands(diff, w["name"]) + return commands + + def del_all_commands(self, obj_in_have): + commands = [] + if not obj_in_have: + return commands + for m in obj_in_have.get("members", []): + commands.append("interface" + " " + m["member"]) + commands.append("no channel-group") + return commands + + def del_intf_commands(self, w, have): + commands = [] + obj_in_have = search_obj_in_list(w["name"], have, "name") + if obj_in_have: + lst_to_del = self.intersect_list_of_dicts(w["members"], obj_in_have["members"]) + if lst_to_del: + for item in lst_to_del: + commands.append("interface" + " " + item["member"]) + commands.append("no channel-group") + return commands diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/lldp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/lldp_global.py new file mode 100644 index 00000000..68d98621 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/lldp_global.py @@ -0,0 +1,277 @@ +# +# -*- 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 nxos_lldp_global class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_diff, + remove_empties, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts + + +class Lldp_global(ConfigBase): + """ + The nxos_lldp_global class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lldp_global"] + + def __init__(self, module): + super(Lldp_global, self).__init__(module) + + def get_lldp_global_facts(self, data=None): + """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, + data=data, + ) + lldp_global_facts = facts["ansible_network_resources"].get("lldp_global") + if not lldp_global_facts: + return {} + return lldp_global_facts + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + commands = list() + warnings = list() + + if self.state in self.ACTION_STATES: + existing_lldp_global_facts = self.get_lldp_global_facts() + else: + existing_lldp_global_facts = {} + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_lldp_global_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_lldp_global_facts = self.get_lldp_global_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_lldp_global_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_lldp_global_facts + if result["changed"]: + result["after"] = changed_lldp_global_facts + + elif self.state == "gathered": + result["gathered"] = changed_lldp_global_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_lldp_global_facts): + """Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params["config"] + have = existing_lldp_global_facts + resp = self.set_state(remove_empties(want), have) + return resp + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params["state"] + if state in ("merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + commands = list() + + if state == "deleted": + commands = self._state_deleted(have) + elif state in ["merged", "rendered"]: + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + return commands + + def _state_replaced(self, want, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + merge_dict = dict_diff(have, want) + # merge_dict will contain new and unique values in want + delete_dict = self.find_delete_params(have, want) + self._module.params["state"] = "deleted" + commands.extend(self._state_deleted(delete_dict)) # delete + self._module.params["state"] = "merged" + commands.extend(self.set_commands(merge_dict)) # merge + self._module.params["state"] = "replaced" + return commands + + def delete_nested_dict(self, have, want): + """ + Returns tlv_select nested dict that needs to be defaulted + """ + outer_dict = {} + for key, val in have.items(): + inner_dict = {} + if not isinstance(val, dict): + if key not in want.keys(): + inner_dict.update({key: val}) + return inner_dict + else: + if key in want.keys(): + outer_dict.update({key: self.delete_nested_dict(val, want[key])}) + else: + outer_dict.update({key: val}) + return outer_dict + + def find_delete_params(self, have, want): + """ + Returns parameters that are present in have and not in want, that need to be defaulted + """ + delete_dict = {} + for key, val in have.items(): + if key not in want.keys(): + delete_dict.update({key: val}) + else: + if key == "tlv_select": + delete_dict.update( + {key: self.delete_nested_dict(have["tlv_select"], want["tlv_select"])}, + ) + return delete_dict + + def _state_merged(self, want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + diff = dict_diff(have, want) + commands.extend(self.set_commands(diff)) + return commands + + def _state_deleted(self, 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 = [] + if have: + for key, val in have.items(): + if "tlv_select" in key: + commands.extend(self.process_nested_dict(val)) + else: + if key == "port_id": + key = "portid-subtype" + commands.append("no lldp " + key + " " + str(val)) + + return commands + + def set_commands(self, diff): + commands = [] + for key, val in diff.items(): + commands.extend(self.add_commands(key, val)) + return commands + + def add_commands(self, key, val): + command = [] + if "port_id" in key: + command.append("lldp portid-subtype " + str(val)) + elif "tlv_select" in key: + command.extend(self.process_nested_dict(val)) + else: + if val: + command.append("lldp " + key + " " + str(val)) + return command + + def process_nested_dict(self, val): + nested_commands = [] + for k, v in val.items(): + if isinstance(v, dict): + for k1, v1 in v.items(): + com1 = "lldp tlv-select " + com2 = "" + if "system" in k: + com2 = "system-" + k1 + elif "management_address" in k: + com2 = "management-address " + k1 + elif "port" in k: + com2 = "port-" + k1 + + com1 += com2 + com1 = self.negate_command(com1, v1) + nested_commands.append(com1) + else: + com1 = "lldp tlv-select " + if "power_management" in k: + com1 += "power-management" + else: + com1 += k + + com1 = self.negate_command(com1, v) + nested_commands.append(com1) + return nested_commands + + def negate_command(self, command, val): + # for merged, replaced vals need to be checked to add 'no' + if self._module.params["state"] == "merged": + if not val: + command = "no " + command + return command diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..528f7497 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,312 @@ +# +# -*- 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 nxos_lldp_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 ( + dict_diff, + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + flatten_dict, + get_interface_type, + normalize_interface, + search_obj_in_list, +) + + +class Lldp_interfaces(ConfigBase): + """ + The nxos_lldp_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lldp_interfaces"] + + def __init__(self, module): + super(Lldp_interfaces, self).__init__(module) + + def get_lldp_interfaces_facts(self, data=None): + """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, + data=data, + ) + lldp_interfaces_facts = facts["ansible_network_resources"].get("lldp_interfaces") + if not lldp_interfaces_facts: + return [] + return lldp_interfaces_facts + + def edit_config(self, commands): + """Wrapper method for `_connection.edit_config()` + This exists solely to allow the unit test framework to mock device connection calls. + """ + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + commands = list() + warnings = list() + state = self._module.params["state"] + action_states = ["merged", "replaced", "deleted", "overridden"] + + if state == "gathered": + result["gathered"] = self.get_lldp_interfaces_facts() + elif state == "rendered": + result["rendered"] = self.set_config({}) + elif state == "parsed": + result["parsed"] = self.set_config({}) + else: + existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() + commands.extend(self.set_config(existing_lldp_interfaces_facts)) + if commands and state in action_states: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + result["before"] = existing_lldp_interfaces_facts + result["commands"] = commands + result["commands"] = commands + + changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts() + + if result["changed"]: + result["after"] = changed_lldp_interfaces_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_lldp_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 + """ + config = self._module.params["config"] + want = [] + if config: + for w in config: + if get_interface_type(w["name"]) not in ( + "management", + "ethernet", + ): + self._module.fail_json( + msg="This module works with either management or ethernet", + ) + w.update({"name": normalize_interface(w["name"])}) + want.append(remove_empties(w)) + have = existing_lldp_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 = [] + state = self._module.params["state"] + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "rendered": + commands = self._state_rendered(want) + elif state == "parsed": + want = self._module.params["running_config"] + commands = self._state_parsed(want) + else: + for w in want: + if state == "merged": + commands.extend(self._state_merged(flatten_dict(w), have)) + elif state == "replaced": + commands.extend(self._state_replaced(flatten_dict(w), have)) + return commands + + def _state_parsed(self, want): + return self.get_lldp_interfaces_facts(want) + + def _state_rendered(self, want): + commands = [] + for w in want: + commands.extend(self.set_commands(w, {})) + return commands + + def _state_gathered(self, have): + """The command generator when state is gathered + + :rtype: A list + :returns: the commands necessary to reproduce the current configuration + """ + commands = [] + want = {} + commands.append(self.set_commands(want, have)) + return commands + + def _state_replaced(self, want, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + del_commands = [] + delete_dict = {} + obj_in_have = flatten_dict(search_obj_in_list(want["name"], have, "name")) + for k1 in obj_in_have.keys(): + if k1 not in want.keys(): + delete_dict.update({k1: obj_in_have[k1]}) + + if delete_dict: + delete_dict.update({"name": want["name"]}) + del_commands = self.del_commands(delete_dict) + merged_commands = self.set_commands(want, have) + + if merged_commands: + cmds = set(del_commands).intersection(set(merged_commands)) + for cmd in cmds: + merged_commands.remove(cmd) + + commands.extend(del_commands) + commands.extend(merged_commands) + return commands + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want_intfs = [w["name"] for w in want] + for h in have: + h = flatten_dict(h) + delete_dict = {} + if h["name"] in want_intfs: + for w in want: + if w["name"] == h["name"]: + delete_keys = list(set(h) - set(flatten_dict(w))) + for k in delete_keys: + delete_dict.update({k: h[k]}) + delete_dict.update({"name": h["name"]}) + break + else: + delete_dict.update(h) + commands.extend(self.del_commands(delete_dict)) + for w in want: + commands.extend(self.set_commands(flatten_dict(w), have)) + return commands + + def _state_merged(self, want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(want, have) + + 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 = [] + if want: + for w in want: + obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name")) + commands.extend(self.del_commands(obj_in_have)) + else: + if not have: + return commands + for h in have: + commands.extend(self.del_commands(flatten_dict(h))) + return commands + + def set_commands(self, want, have): + commands = [] + obj_in_have = flatten_dict(search_obj_in_list(want["name"], have, "name")) + if not obj_in_have: + commands = self.add_commands(flatten_dict(want)) + else: + diff = dict_diff(obj_in_have, want) + if diff: + diff.update({"name": want["name"]}) + commands = self.add_commands(diff) + return commands + + def add_commands(self, d): + commands = [] + if not d: + return commands + commands.append("interface " + d["name"]) + if "transmit" in d: + if d["transmit"]: + commands.append("lldp transmit") + else: + commands.append("no lldp transmit") + if "receive" in d: + if d["receive"]: + commands.append("lldp receive") + else: + commands.append("no lldp receive") + if "management_address" in d: + commands.append("lldp tlv-set management-address " + d["management_address"]) + if "vlan" in d: + commands.append("lldp tlv-set vlan " + str(d["vlan"])) + + return commands + + def del_commands(self, obj): + commands = [] + if not obj or len(obj.keys()) == 1: + return commands + commands.append("interface " + obj["name"]) + if "transmit" in obj: + commands.append("lldp transmit") + if "receive" in obj: + commands.append("lldp receive") + if "management_address" in obj: + commands.append("no lldp tlv-set management-address " + obj["management_address"]) + if "vlan" in obj: + commands.append("no lldp tlv-set vlan " + str(obj["vlan"])) + + return commands diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/logging_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/logging_global.py new file mode 100644 index 00000000..6e493ad3 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/logging_global.py @@ -0,0 +1,199 @@ +# +# -*- 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) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_logging_global config file. +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 its desired end-state is +created. +""" + +from copy import deepcopy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + get_from_dict, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.logging_global import ( + Logging_globalTemplate, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_logging_sevmap, +) + + +class Logging_global(ResourceModule): + """ + The nxos_logging_global config class + """ + + def __init__(self, module): + super(Logging_global, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="logging_global", + tmplt=Logging_globalTemplate(), + ) + self._sev_map = get_logging_sevmap(invert=True) + self._state_set = ("replaced", "deleted", "overridden") + self.parsers = [ + "console", + "module", + "monitor", + "logfile", + "event.link_status.enable", + "event.link_status.default", + "event.trunk_status.enable", + "event.trunk_status.default", + "history.severity", + "history.size", + "ip.access_list.cache.entries", + "ip.access_list.cache.interval", + "ip.access_list.cache.threshold", + "ip.access_list.detailed", + "ip.access_list.include.sgt", + "origin_id.hostname", + "origin_id.ip", + "origin_id.string", + "rate_limit", + "rfc_strict", + "source_interface", + "timestamp", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = self._logging_list_to_dict(self.want) + haved = self._logging_list_to_dict(self.have) + + if self.state == "deleted": + # empty out want (in case something was specified) + # some items are populated later on for correct removal + wantd = {} + + # pre-process `event.x.y` keys + for x in self.parsers[4:7]: + have_k = get_from_dict(haved, x) + want_k = get_from_dict(wantd, x) + if have_k is None and want_k is not None: + # set have to True to mimic default state + # this allows negate commands to be issued + self.__update_dict(haved, x) + if all( + ( + self.state in self._state_set, + have_k is False, + want_k is None, + ), + ): + # if want is missing and have is negated + # set want to True in order to revert to default state + self.__update_dict(wantd, x) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + for x in self.parsers[0:4]: + hstate = haved.get(x, {}).get("state", "") + wstate = wantd.get(x, {}).get("state", "") + if hstate == "disabled" and not wstate: + # this ensures that updates are done + # with correct `state` + if wantd.get(x, {}): + wantd[x].update({"state": "enabled"}) + wantd = dict_merge(haved, wantd) + + if self.state in self._state_set: + # set default states for keys that appear in negated form + for x in self.parsers[0:3]: + if x in haved and x not in wantd: + wantd[x] = {"state": "enabled"} + if "rate_limit" in haved and "rate_limit" not in wantd: + wantd["rate_limit"] = "enabled" + if "logfile" in haved and "logfile" not in wantd: + wantd["logfile"] = {"name": "messages", "severity": 5} + + self._compare(want=wantd, have=haved) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Logging_global network resource. + """ + self.compare(parsers=self.parsers, want=want, have=have) + self._compare_lists(want, have) + + def _compare_lists(self, want, have): + """Compare list of dictionaries""" + for x in ["facilities", "hosts"]: + wantx = want.get(x, {}) + havex = have.get(x, {}) + for key, wentry in iteritems(wantx): + hentry = havex.pop(key, {}) + if wentry != hentry: + if x == "hosts" and self.state in self._state_set: + # remove have config for hosts + # else want gets appended + self.addcmd(hentry, x, negate=True) + self.addcmd(wentry, x) + for key, hentry in iteritems(havex): + self.addcmd(hentry, x, negate=True) + + def _logging_list_to_dict(self, data): + """Convert all list to dicts to dicts + of dicts and substitute severity values + """ + tmp = deepcopy(data) + pkey = {"hosts": "host", "facilities": "facility"} + for k in ("hosts", "facilities"): + if k in tmp: + for x in tmp[k]: + if "severity" in x: + x["severity"] = self._sev_map[x["severity"]] + tmp[k] = {i[pkey[k]]: i for i in tmp[k]} + for k in ("console", "history", "logfile", "module", "monitor"): + if "severity" in tmp.get(k, {}): + tmp[k]["severity"] = self._sev_map[tmp[k]["severity"]] + return tmp + + def __update_dict(self, datadict, key, nval=True): + """Utility method that updates last subkey of + `datadict` as identified by `key` to `nval`. + """ + keys = key.split(".") + if keys[0] not in datadict: + datadict[keys[0]] = {} + if keys[1] not in datadict[keys[0]]: + datadict[keys[0]][keys[1]] = {} + datadict[keys[0]][keys[1]].update({keys[2]: nval}) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/ntp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/ntp_global.py new file mode 100644 index 00000000..57a1a58d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/ntp_global.py @@ -0,0 +1,161 @@ +# +# -*- 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) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_ntp_global config file. +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 its desired end-state is +created. +""" + +from copy import deepcopy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ntp_global import ( + Ntp_globalTemplate, +) + + +class Ntp_global(ResourceModule): + """ + The nxos_ntp_global config class + """ + + def __init__(self, module): + super(Ntp_global, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="ntp_global", + tmplt=Ntp_globalTemplate(), + ) + self.parsers = [ + "access_group.match_all", + "allow.control.rate_limit", + "allow.private", + "authenticate", + "logging", + "master.stratum", + "passive", + "source", + "source_interface", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = self._ntp_list_to_dict(self.want) + haved = self._ntp_list_to_dict(self.have) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd + if self.state == "deleted": + wantd = {} + + self._compare(want=wantd, have=haved) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Ntp_global network resource. + """ + self.compare(parsers=self.parsers, want=want, have=have) + self._compare_lists(want=want, have=have) + self._compare_access_group(want=want, have=have) + + def _compare_lists(self, want, have): + keys = ["authentication_keys", "peers", "servers", "trusted_keys"] + for x in keys: + wantx = want.get(x, {}) + havex = have.get(x, {}) + + for wkey, wentry in iteritems(wantx): + hentry = havex.pop(wkey, {}) + + # pop aliased keys to preserve idempotence + if x in ["peers", "servers"]: + wentry.pop("use_vrf", None) + + if wentry != hentry: + if x in keys[1:3] and self.state in [ + "overridden", + "replaced", + ]: + # remove existing config else it gets appeneded + self.addcmd(hentry, x, negate=True) + self.addcmd(wentry, x) + + # remove superfluos config + for _hkey, hentry in iteritems(havex): + self.addcmd(hentry, x, negate=True) + + def _compare_access_group(self, want, have): + want_ag = want.get("access_group", {}) + have_ag = have.get("access_group", {}) + + for x in ["peer", "query_only", "serve", "serve_only"]: + wx = want_ag.get(x, {}) + hx = have_ag.get(x, {}) + + for wkey, wentry in iteritems(wx): + hentry = hx.pop(wkey, {}) + if wentry != hentry: + self.addcmd(wentry, x) + + # remove superfluos config + for hentry in hx.values(): + self.addcmd(hentry, x, negate=True) + + def _ntp_list_to_dict(self, data): + """Convert all list to dicts to dicts + of dicts + """ + tmp = deepcopy(data) + if "access_group" in tmp: + for x in ["peer", "query_only", "serve", "serve_only"]: + if x in tmp["access_group"]: + tmp["access_group"][x] = {i["access_list"]: i for i in tmp["access_group"][x]} + pkey = { + "authentication_keys": "id", + "peers": "peer", + "servers": "server", + "trusted_keys": "key_id", + } + for k in pkey.keys(): + if k in tmp: + tmp[k] = {i[pkey[k]]: i for i in tmp[k]} + return tmp diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/ospf_interfaces.py new file mode 100644 index 00000000..0a1bc580 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/ospf_interfaces.py @@ -0,0 +1,204 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# 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 + +""" +The nxos_ospf_interfaces config file. +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 its desired end-state is +created. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospf_interfaces import ( + Ospf_interfacesTemplate, +) + + +class Ospf_interfaces(ResourceModule): + """ + The nxos_ospf_interfaces config class + """ + + def __init__(self, module): + super(Ospf_interfaces, self).__init__( + empty_fact_val=[], + facts_module=Facts(module), + module=module, + resource="ospf_interfaces", + tmplt=Ospf_interfacesTemplate(), + ) + self.parsers = [ + "authentication", + "authentication_key", + "message_digest_key", + "cost", + "dead_interval", + "hello_interval", + "instance", + "mtu_ignore", + "network", + "passive_interface", + "priority", + "retransmit_interval", + "shutdown", + "transmit_delay", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {entry["name"]: entry for entry in self.want} + haved = {entry["name"]: entry for entry in self.have} + + # turn all lists of dicts into dicts prior to merge + for entry in wantd, haved: + self._list_to_dict(entry) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + wantd = {} + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self._compare(want={}, have=have) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Ospf_interfaces network resource. + """ + begin = len(self.commands) + self._compare_ospf_interfaces(want, have) + if len(self.commands) != begin: + self.commands.insert(begin, self._tmplt.render(want or have, "interface", False)) + + def _compare_ospf_interfaces(self, want, have): + waf = want.get("address_family", {}) + haf = have.get("address_family", {}) + + for afi in ("ipv4", "ipv6"): + witem = waf.pop(afi, {}) + hitem = haf.pop(afi, {}) + + # this key needs to be compared separately and + # popped from `authentication` dict to + # preserve idempotence for other keys in this dict + self.compare(["authentication.key_chain"], want=witem, have=hitem) + witem.get("authentication", {}).pop("key_chain", None) + hitem.get("authentication", {}).pop("key_chain", None) + + # this ensures that the "no" form of "ip ospf passive-interface" + # command is executed even when there is no existing config + if witem.get("passive_interface") is False and "passive_interface" not in hitem: + hitem["passive_interface"] = True + + if "passive_interface" in hitem and witem.get("default_passive_interface"): + self.commands.append(self._generate_passive_intf(witem)) + + self.compare(parsers=self.parsers, want=witem, have=hitem) + + # compare top-level `multi_areas` config + for area in witem.get("multi_areas", []): + if area not in hitem.get("multi_areas", []): + self.addcmd({"afi": afi, "area": area}, "multi_areas", negate=False) + # remove superfluous top-level `multi_areas` config + for area in hitem.get("multi_areas", []): + if area not in witem.get("multi_areas", []): + self.addcmd({"afi": afi, "area": area}, "multi_areas", negate=True) + + # compare config->address_family->processes + self._compare_processes(afi, witem.get("processes", {}), hitem.get("processes", {})) + + def _compare_processes(self, afi, want, have): + # add and update config->address_family->processes + + for w_id, wproc in want.items(): + hproc = have.pop(w_id, {}) + hproc["afi"] = wproc["afi"] = afi + + # compare config->address_family->processes->area + self.compare(["area"], wproc, hproc) + + # compare config->address_family->processes->multi_areas + marea_dict = {"afi": afi, "process_id": wproc["process_id"]} + for area in wproc.get("multi_areas", []): + if area not in hproc.get("multi_areas", []): + marea_dict["area"] = area + self.addcmd(marea_dict, "processes_multi_areas", negate=False) + # remove superfluous processes->multi_areas config + for area in hproc.get("multi_areas", []): + if area not in wproc.get("multi_areas", []): + marea_dict["area"] = area + self.addcmd(marea_dict, "processes_multi_areas", negate=True) + + # remove superflous config->address_family->processes + for hproc in have.values(): + hproc["afi"] = afi + + # remove config->address_family->processes->area + self.addcmd(hproc, "area", negate=True) + + # remove superfluous processes->multi_areas config + marea_dict = {"afi": afi, "process_id": hproc["process_id"]} + for area in hproc.get("multi_areas", []): + marea_dict["area"] = area + self.addcmd(marea_dict, "processes_multi_areas", negate=True) + + def _list_to_dict(self, entry): + for item in entry.values(): + for ag in item.get("address_family", []): + ag["processes"] = { + subentry["process_id"]: subentry for subentry in ag.get("processes", []) + } + item["address_family"] = { + subentry["afi"]: subentry for subentry in item.get("address_family", []) + } + + def _generate_passive_intf(self, data): + cmd = "default " + if data["afi"] == "ipv4": + cmd += "ip ospf passive-interface" + else: + cmd += "ospfv3 passive-interface" + return cmd diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/ospfv2.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/ospfv2.py new file mode 100644 index 00000000..8a6f42e7 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/ospfv2.py @@ -0,0 +1,216 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_ospfv2 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 ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + get_from_dict, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv2 import ( + Ospfv2Template, +) + + +class Ospfv2(ResourceModule): + """ + The nxos_ospfv2 class + """ + + def __init__(self, module): + super(Ospfv2, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="ospfv2", + tmplt=Ospfv2Template(), + ) + self.parsers = [ + "router_id", + "auto_cost", + "graceful_restart.set", + "graceful_restart.helper_disable", + "isolate", + "log_adjacency_changes", + "max_lsa", + "mpls.traffic_eng.router_id", + "mpls.traffic_eng.multicast_intact", + "name_lookup", + "passive_interface.default", + "rfc1583compatibility", + "shutdown", + "default_information.originate", + "default_metric", + "distance", + "table_map", + "timers.lsa_arrival", + "timers.lsa_group_pacing", + "timers.throttle.lsa", + "timers.throttle.spf", + "maximum_paths", + "max_metric", + "down_bit_ignore", + "capability.vrf_lite", + "bfd", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.gen_config() + self.run_commands() + return self.result + + def gen_config(self): + """Select the appropriate function based on the state provided + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + wantd = {(entry["process_id"]): entry for entry in self.want.get("processes", [])} + haved = {(entry["process_id"]): entry for entry in self.have.get("processes", [])} + + # turn all lists of dicts into dicts prior to merge + for entry in wantd, haved: + self._ospf_list_to_dict(entry) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + wantd = {} + + # if state is overridden, first remove processes that are in have but not in want + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self.addcmd(have, "process_id", True) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + begin = len(self.commands) + self.compare(self.parsers, want=want, have=have) + self._compare_lists(want=want, have=have) + self._areas_compare(want=want, have=have) + self._vrfs_compare(want=want, have=have) + + if len(self.commands) != begin or (not have and want): + self.commands.insert( + begin, + self._tmplt.render( + want or have, + "vrf" if "vrf" in (want.keys() or have.keys()) else "process_id", + False, + ), + ) + + def _areas_compare(self, want, have): + wareas = want.get("areas", {}) + hareas = have.get("areas", {}) + for name, entry in iteritems(wareas): + self._area_compare(want=entry, have=hareas.pop(name, {})) + for name, entry in iteritems(hareas): + self._area_compare(want={}, have=entry) + + def _area_compare(self, want, have): + parsers = [ + "area.default_cost", + "area.authentication", + "area.nssa", + "area.nssa.translate", + "area.stub", + ] + self.compare(parsers=parsers, want=want, have=have) + self._area_compare_lists(want=want, have=have) + + def _area_compare_lists(self, want, have): + for attrib in ["filter_list", "ranges"]: + wdict = want.get(attrib, {}) + hdict = have.get(attrib, {}) + for key, entry in iteritems(wdict): + if entry != hdict.pop(key, {}): + entry["area_id"] = want["area_id"] + self.addcmd(entry, "area.{0}".format(attrib), False) + # remove remaining items in have for replaced + for entry in hdict.values(): + entry["area_id"] = have["area_id"] + self.addcmd(entry, "area.{0}".format(attrib), True) + + def _compare_lists(self, want, have): + for attrib in [ + "summary_address", + "redistribute", + "mpls.traffic_eng.areas", + ]: + wdict = get_from_dict(want, attrib) or {} + hdict = get_from_dict(have, attrib) or {} + + for key, entry in iteritems(wdict): + if entry != hdict.pop(key, {}): + self.addcmd(entry, attrib, False) + # remove remaining items in have for replaced + for entry in hdict.values(): + self.addcmd(entry, attrib, True) + + def _vrfs_compare(self, want, have): + wvrfs = want.get("vrfs", {}) + hvrfs = have.get("vrfs", {}) + for name, entry in iteritems(wvrfs): + self._compare(want=entry, have=hvrfs.pop(name, {})) + # remove remaining items in have for replaced + for name, entry in iteritems(hvrfs): + self.addcmd(entry, "vrf", True) + + def _ospf_list_to_dict(self, entry): + for _pid, proc in iteritems(entry): + for area in proc.get("areas", []): + area["ranges"] = {entry["prefix"]: entry for entry in area.get("ranges", [])} + area["filter_list"] = { + entry["direction"]: entry for entry in area.get("filter_list", []) + } + mpls_areas = { + entry["area_id"]: entry + for entry in proc.get("mpls", {}).get("traffic_eng", {}).get("areas", []) + } + if mpls_areas: + proc["mpls"]["traffic_eng"]["areas"] = mpls_areas + proc["areas"] = {entry["area_id"]: entry for entry in proc.get("areas", [])} + proc["summary_address"] = { + entry["prefix"]: entry for entry in proc.get("summary_address", []) + } + proc["redistribute"] = { + (entry.get("id"), entry["protocol"]): entry + for entry in proc.get("redistribute", []) + } + if "vrfs" in proc: + proc["vrfs"] = {entry["vrf"]: entry for entry in proc.get("vrfs", [])} + self._ospf_list_to_dict(proc["vrfs"]) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/ospfv3.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/ospfv3.py new file mode 100644 index 00000000..e60fc31a --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/ospfv3.py @@ -0,0 +1,230 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# 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 + +""" +The nxos_ospfv3 config file. +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 its desired end-state is +created. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + get_from_dict, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv3 import ( + Ospfv3Template, +) + + +class Ospfv3(ResourceModule): + """ + The nxos_ospfv3 config class + """ + + def __init__(self, module): + super(Ospfv3, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="ospfv3", + tmplt=Ospfv3Template(), + ) + self.parsers = [ + "auto_cost", + "flush_routes", + "graceful_restart.set", + "graceful_restart.helper_disable", + "graceful_restart.grace_period", + "graceful_restart.planned_only", + "isolate", + "log_adjacency_changes", + "max_lsa", + "max_metric", + "name_lookup", + "passive_interface.default", + "router_id", + "shutdown", + "timers.lsa_arrival", + "timers.lsa_group_pacing", + "timers.throttle.lsa", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {(entry["process_id"]): entry for entry in self.want.get("processes", [])} + haved = {(entry["process_id"]): entry for entry in self.have.get("processes", [])} + + # turn all lists of dicts into dicts prior to merge + for entry in wantd, haved: + self._ospfv3_list_to_dict(entry) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + wantd = {} + + # if state is overridden, first remove processes that are in have but not in want + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self.addcmd(have, "process_id", True) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Ospfv3 network resource. + """ + begin = len(self.commands) + self.compare(parsers=self.parsers, want=want, have=have) + self._areas_compare(want=want, have=have) + self._vrfs_compare(want=want, have=have) + self._af_compare(want=want, have=have) + + if len(self.commands) != begin or (not have and want): + self.commands.insert( + begin, + self._tmplt.render( + want or have, + "vrf" if "vrf" in (want.keys() or have.keys()) else "process_id", + False, + ), + ) + + def _areas_compare(self, want, have): + wareas = want.get("areas", {}) + hareas = have.get("areas", {}) + for name, entry in iteritems(wareas): + self._area_compare(want=entry, have=hareas.pop(name, {})) + for name, entry in iteritems(hareas): + self._area_compare(want={}, have=entry) + + def _area_compare(self, want, have): + parsers = ["area.nssa", "area.nssa.translate", "area.stub"] + self.compare(parsers=parsers, want=want, have=have) + + def _vrfs_compare(self, want, have): + wvrfs = want.get("vrfs", {}) + hvrfs = have.get("vrfs", {}) + for name, entry in iteritems(wvrfs): + self._compare(want=entry, have=hvrfs.pop(name, {})) + # remove remaining items in have for replaced + for name, entry in iteritems(hvrfs): + self.addcmd(entry, "vrf", True) + + def _af_compare(self, want, have): + parsers = [ + "default_information.originate", + "distance", + "maximum_paths", + "table_map", + "timers.throttle.spf", + ] + waf = want.get("address_family", {}) + haf = have.get("address_family", {}) + + cmd_ptr = len(self.commands) + + self._af_areas_compare(want=waf, have=haf) + self._af_compare_lists(want=waf, have=haf) + self.compare(parsers=parsers, want=waf, have=haf) + + cmd_ptr_nxt = len(self.commands) + if cmd_ptr < cmd_ptr_nxt: + self.commands.insert(cmd_ptr, "address-family ipv6 unicast") + + def _af_areas_compare(self, want, have): + wareas = want.get("areas", {}) + hareas = have.get("areas", {}) + for name, entry in iteritems(wareas): + self._af_area_compare(want=entry, have=hareas.pop(name, {})) + for name, entry in iteritems(hareas): + self._af_area_compare(want={}, have=entry) + + def _af_area_compare(self, want, have): + self.compare(parsers=["area.default_cost"], want=want, have=have) + self._af_area_compare_lists(want=want, have=have) + + def _af_area_compare_lists(self, want, have): + for attrib in ["filter_list", "ranges"]: + wdict = want.get(attrib, {}) + hdict = have.get(attrib, {}) + for key, entry in iteritems(wdict): + if entry != hdict.pop(key, {}): + entry["area_id"] = want["area_id"] + self.addcmd(entry, "area.{0}".format(attrib), False) + # remove remaining items in have for replaced + for entry in hdict.values(): + entry["area_id"] = have["area_id"] + self.addcmd(entry, "area.{0}".format(attrib), True) + + def _af_compare_lists(self, want, have): + for attrib in ["summary_address", "redistribute"]: + wdict = get_from_dict(want, attrib) or {} + hdict = get_from_dict(have, attrib) or {} + + for key, entry in iteritems(wdict): + if entry != hdict.pop(key, {}): + self.addcmd(entry, attrib, False) + # remove remaining items in have for replaced + for entry in hdict.values(): + self.addcmd(entry, attrib, True) + + def _ospfv3_list_to_dict(self, entry): + for _pid, proc in iteritems(entry): + proc["areas"] = {entry["area_id"]: entry for entry in proc.get("areas", [])} + af = proc.get("address_family") + if af: + for area in af.get("areas", []): + area["ranges"] = {entry["prefix"]: entry for entry in area.get("ranges", [])} + area["filter_list"] = { + entry["direction"]: entry for entry in area.get("filter_list", []) + } + af["areas"] = {entry["area_id"]: entry for entry in af.get("areas", [])} + af["summary_address"] = { + entry["prefix"]: entry for entry in af.get("summary_address", []) + } + af["redistribute"] = { + (entry.get("id"), entry["protocol"]): entry + for entry in af.get("redistribute", []) + } + if "vrfs" in proc: + proc["vrfs"] = {entry["vrf"]: entry for entry in proc.get("vrfs", [])} + self._ospfv3_list_to_dict(proc["vrfs"]) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/prefix_lists.py new file mode 100644 index 00000000..2c6bb814 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/prefix_lists.py @@ -0,0 +1,146 @@ +# +# -*- 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) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_prefix_lists config file. +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 its desired end-state is +created. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.prefix_lists import ( + Prefix_listsTemplate, +) + + +class Prefix_lists(ResourceModule): + """ + The nxos_prefix_lists config class + """ + + def __init__(self, module): + super(Prefix_lists, self).__init__( + empty_fact_val=[], + facts_module=Facts(module), + module=module, + resource="prefix_lists", + tmplt=Prefix_listsTemplate(), + ) + self.parsers = [] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {entry["afi"]: entry for entry in self.want} + haved = {entry["afi"]: entry for entry in self.have} + + self._prefix_list_transform(wantd) + self._prefix_list_transform(haved) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + for key, hvalue in iteritems(haved): + wvalue = wantd.pop(key, {}) + if wvalue: + wplists = wvalue.get("prefix_lists", {}) + hplists = hvalue.get("prefix_lists", {}) + hvalue["prefix_lists"] = { + k: v for k, v in iteritems(hplists) if k in wplists or not wplists + } + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self._compare(want={}, have=have) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Prefix_lists network resource. + """ + wplists = want.get("prefix_lists", {}) + hplists = have.get("prefix_lists", {}) + for wk, wentry in iteritems(wplists): + hentry = hplists.pop(wk, {}) + self.compare(["description"], want=wentry, have=hentry) + # compare sequences + self._compare_seqs(wentry.pop("entries", {}), hentry.pop("entries", {})) + + if self.state in ["overridden", "deleted"]: + # remove remaining prefix lists + for h in hplists.values(): + self.commands.append( + "no {0} prefix-list {1}".format(h["afi"].replace("ipv4", "ip"), h["name"]), + ) + + def _compare_seqs(self, want, have): + for wseq, wentry in iteritems(want): + hentry = have.pop(wseq, {}) + if hentry != wentry: + if hentry: + if self.state == "merged": + self._module.fail_json( + msg="Cannot update existing sequence {0} of prefix list {1} with state merged." + " Please use state replaced or overridden.".format( + hentry["sequence"], + hentry["name"], + ), + ) + else: + self.addcmd(hentry, "entry", negate=True) + self.addcmd(wentry, "entry") + # remove remaining entries from have prefix list + for hseq in have.values(): + self.addcmd(hseq, "entry", negate=True) + + def _prefix_list_transform(self, entry): + for afi, value in iteritems(entry): + if "prefix_lists" in value: + for plist in value["prefix_lists"]: + plist.update({"afi": afi}) + if "entries" in plist: + for seq in plist["entries"]: + seq.update({"afi": afi, "name": plist["name"]}) + plist["entries"] = {x["sequence"]: x for x in plist["entries"]} + value["prefix_lists"] = {entry["name"]: entry for entry in value["prefix_lists"]} diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/route_maps.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/route_maps.py new file mode 100644 index 00000000..b098b065 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/route_maps.py @@ -0,0 +1,192 @@ +# +# -*- 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) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_route_maps config file. +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 its desired end-state is +created. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + get_from_dict, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.route_maps import ( + Route_mapsTemplate, +) + + +class Route_maps(ResourceModule): + """ + The nxos_route_maps config class + """ + + def __init__(self, module): + super(Route_maps, self).__init__( + empty_fact_val=[], + facts_module=Facts(module), + module=module, + resource="route_maps", + tmplt=Route_mapsTemplate(), + ) + self.linear_parsers = [ + "description", + "continue_sequence", + "set.as_path.prepend.last_as", + "set.as_path.tag", + "set.comm_list", + "set.dampening", + "set.extcomm_list", + "set.forwarding_address", + "set.null_interface", + "set.ip.address.prefix_list", + "set.ip.precedence", + "set.ipv6.address.prefix_list", + "set.ipv6.precedence", + "set.label_index", + "set.level", + "set.local_preference", + "set.metric", + "set.metric_type", + "set.nssa_only", + "set.origin", + "set.path_selection", + "set.tag", + "set.weight", + ] + self.complex_parsers = [ + "match.as_number.asn", + "match.as_number.as_path_list", + "match.as_path", + "match.community.community_list", + "match.evpn.route_types", + "match.extcommunity.extcommunity_list", + "match.interfaces", + "match.ip.address.access_list", + "match.ip.address.prefix_lists", + "match.ip.multicast", + "match.ip.next_hop.prefix_lists", + "match.ip.route_source.prefix_lists", + "match.ipv6.address.access_list", + "match.ipv6.address.prefix_lists", + "match.ipv6.multicast", + "match.ipv6.next_hop.prefix_lists", + "match.ipv6.route_source.prefix_lists", + "match.mac_list", + "match.metric", + "match.ospf_area", + "match.route_types", + "match.source_protocol", + "match.tags", + "set.as_path.prepend.as_number", + "set.distance", + "set.evpn.gateway_ip", + "set.community", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = self._route_maps_list_to_dict(self.want) + haved = self._route_maps_list_to_dict(self.have) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + wantd = {} + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + for _hk, hentry in iteritems(have.get("entries", {})): + self.commands.append(self._tmplt.render(hentry, "route_map", True)) + + for wk, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(wk, {})) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Route_maps network resource. + """ + w_entries = want.get("entries", {}) + h_entries = have.get("entries", {}) + self._compare_entries(want=w_entries, have=h_entries) + + def _compare_entries(self, want, have): + for wk, wentry in iteritems(want): + hentry = have.pop(wk, {}) + begin = len(self.commands) + + self._compare_lists(wentry, hentry) + self.compare(parsers=self.linear_parsers, want=wentry, have=hentry) + + if len(self.commands) != begin: + self.commands.insert(begin, self._tmplt.render(wentry, "route_map", False)) + # remove superfluos entries from have + for _hk, hentry in iteritems(have): + self.commands.append(self._tmplt.render(hentry, "route_map", True)) + + def _compare_lists(self, want, have): + for x in self.complex_parsers: + wx = get_from_dict(want, x) or [] + hx = get_from_dict(have, x) or [] + + if isinstance(wx, list): + wx = set(wx) + if isinstance(hx, list): + hx = set(hx) + + if wx != hx: + # negate existing config so that want is not appended + # in case of replaced or overridden + if self.state in ["replaced", "overridden"] and hx: + self.addcmd(have, x, negate=True) + self.addcmd(want, x) + + def _route_maps_list_to_dict(self, entry): + entry = {x["route_map"]: x for x in entry} + for rmap, data in iteritems(entry): + if "entries" in data: + for x in data["entries"]: + x.update({"route_map": rmap}) + data["entries"] = { + (rmap, entry["action"], entry.get("sequence")): entry + for entry in data["entries"] + } + return entry diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/snmp_server.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/snmp_server.py new file mode 100644 index 00000000..472e7285 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/snmp_server.py @@ -0,0 +1,243 @@ +# +# -*- 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) +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos_snmp_server config file. +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 its desired end-state is +created. +""" + +from copy import deepcopy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + get_from_dict, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.snmp_server import ( + Snmp_serverTemplate, +) + + +class Snmp_server(ResourceModule): + """ + The nxos_snmp_server config class + """ + + def __init__(self, module): + super(Snmp_server, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="snmp_server", + tmplt=Snmp_serverTemplate(), + ) + self.parsers = [ + "aaa_user.cache_timeout", + "contact", + "context", + "counter.enable", + "counter.cache.timeout", + "drop.unknown_engine_id", + "drop.unknown_user", + "traps.aaa", + "traps.bgp", + "traps.bridge.newroot", + "traps.bridge.topologychange", + "traps.callhome.event_notify", + "traps.callhome.smtp_send_fail", + "traps.cfs.merge_failure", + "traps.cfs.state_change_notif", + "traps.config.ccmCLIRunningConfigChanged", + "traps.entity.cefcMIBEnableStatusNotification", + "traps.entity.entity_fan_status_change", + "traps.entity.entity_mib_change", + "traps.entity.entity_module_inserted", + "traps.entity.entity_module_status_change", + "traps.entity.entity_power_out_change", + "traps.entity.entity_power_status_change", + "traps.entity.entity_sensor", + "traps.entity.entity_unrecognised_module", + "traps.feature_control.featureOpStatusChange", + "traps.feature_control.ciscoFeatOpStatusChange", + "traps.generic.coldStart", + "traps.generic.warmStart", + "traps.license.notify_license_expiry", + "traps.license.notify_license_expiry_warning", + "traps.license.notify_licensefile_missing", + "traps.license.notify_no_license_for_feature", + "traps.link.cErrDisableInterfaceEventRev1", + "traps.link.cieLinkDown", + "traps.link.cieLinkUp", + "traps.link.cisco_xcvr_mon_status_chg", + "traps.link.cmn_mac_move_notification", + "traps.link.delayed_link_state_change", + "traps.link.extended_linkDown", + "traps.link.extended_linkUp", + "traps.link.linkDown", + "traps.link.linkUp", + "traps.mmode.cseMaintModeChangeNotify", + "traps.mmode.cseNormalModeChangeNotify", + "traps.ospf", + "traps.ospfv3", + "traps.rf.redundancy_framework", + "traps.rmon.fallingAlarm", + "traps.rmon.hcFallingAlarm", + "traps.rmon.hcRisingAlarm", + "traps.rmon.risingAlarm", + "traps.snmp.authentication", + "traps.storm_control.cpscEventRev1", + "traps.storm_control.trap_rate", + "traps.stpx.inconsistency", + "traps.stpx.root_inconsistency", + "traps.stpx.loop_inconsistency", + "traps.syslog.message_generated", + "traps.sysmgr.cseFailSwCoreNotifyExtended", + "traps.system.clock_change_notification", + "traps.upgrade.upgradeJobStatusNotify", + "traps.upgrade.upgradeOpNotifyOnCompletion", + "traps.vtp.notifs", + "traps.vtp.vlancreate", + "traps.vtp.vlandelete", + "engine_id.local", + "global_enforce_priv", + "location", + "mib.community_map", + "packetsize", + "protocol.enable", + "source_interface.informs", + "source_interface.traps", + "system_shutdown", + "tcp_session", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = self._list_to_dict(self.want) + haved = self._list_to_dict(self.have) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # this ensures that if user sets `enable: True` for a trap + # all suboptions for that trap are set to True + for x in [ + "traps.aaa", + "traps.bridge", + "traps.callhome", + "traps.cfs", + "traps.config", + "traps.entity", + "traps.feature_control", + "traps.generic", + "traps.license", + "traps.link", + "traps.mmode", + "traps.rf", + "traps.rmon", + "traps.snmp", + "traps.storm_control", + "traps.stpx", + "traps.syslog", + "traps.sysmgr", + "traps.system", + "traps.upgrade", + "traps.vtp", + ]: + entry = get_from_dict(wantd, x) + if entry and entry.get("enable", False): + key = x.split(".") + wantd[key[0]][key[1]].pop("enable") + for i in self.parsers: + if i.startswith(x): + key = i.split(".") + wantd[key[0]][key[1]][key[2]] = True + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + wantd = {} + + self._compare(want=wantd, have=haved) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Snmp_server network resource. + """ + self.compare(parsers=self.parsers, want=want, have=have) + self._compare_lists(want=want, have=have) + + def _compare_lists(self, want, have): + """ + Compare list of dictionaries + """ + for x in ["users.auth", "users.use_acls", "hosts", "communities"]: + wantx = get_from_dict(want, x) or {} + havex = get_from_dict(have, x) or {} + for wkey, wentry in iteritems(wantx): + hentry = havex.pop(wkey, {}) + if wentry != hentry: + self.addcmd(wentry, x) + # remove superfluous items + for _k, hv in iteritems(havex): + self.addcmd(hv, x, negate=True) + + def _list_to_dict(self, data): + def _build_key(x): + key = set() + for k, v in iteritems(x): + if isinstance(v, dict): + for sk, sv in iteritems(v): + if isinstance(sv, dict): + for ssk, ssv in iteritems(sv): + key.add(sk + "_" + ssk + "_" + str(ssv)) + else: + key.add(sk + "_" + str(sv)) + else: + key.add(k + "_" + str(v)) + return tuple(sorted(key)) + + tmp = deepcopy(data) + if "communities" in tmp: + tmp["communities"] = {_build_key(entry): entry for entry in tmp["communities"]} + if "users" in tmp: + if "auth" in tmp["users"]: + tmp["users"]["auth"] = {_build_key(entry): entry for entry in tmp["users"]["auth"]} + if "use_acls" in tmp["users"]: + tmp["users"]["use_acls"] = { + entry["user"]: entry for entry in tmp["users"]["use_acls"] + } + if "hosts" in tmp: + tmp["hosts"] = {_build_key(entry): entry for entry in tmp["hosts"]} + return tmp diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/static_routes.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/static_routes.py new file mode 100644 index 00000000..721ff82c --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/static_routes.py @@ -0,0 +1,567 @@ +# +# -*- 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 nxos_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 copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + normalize_interface, + search_obj_in_list, +) + + +class Static_routes(ConfigBase): + """ + The nxos_xstatic_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, data=None): + """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, + data=data, + ) + static_routes_facts = facts["ansible_network_resources"].get("static_routes") + if not static_routes_facts: + return [] + + return static_routes_facts + + def edit_config(self, commands): + """Wrapper method for `_connection.edit_config()` + This exists solely to allow the unit test framework to mock device connection calls. + """ + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + state = self._module.params["state"] + action_states = ["merged", "replaced", "deleted", "overridden"] + + if state == "gathered": + result["gathered"] = self.get_static_routes_facts() + elif state == "rendered": + result["rendered"] = self.set_config({}) + # no need to fetch facts for rendered + elif state == "parsed": + result["parsed"] = self.set_config({}) + # no need to fetch facts for parsed + else: + existing_static_routes_facts = self.get_static_routes_facts() + commands.extend(self.set_config(existing_static_routes_facts)) + if commands and state in action_states: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + result["before"] = existing_static_routes_facts + result["commands"] = commands + + changed_static_routes_facts = self.get_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 + """ + config = self._module.params["config"] + want = [] + if config: + for w in config: + want.append(remove_empties(w)) + have = existing_static_routes_facts + want = self.add_default_vrf(deepcopy(want)) + have = self.add_default_vrf(deepcopy(have)) + 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"] + commands = [] + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "rendered": + commands = self._state_rendered(want, have=[]) + elif state == "parsed": + want = self._module.params["running_config"] + commands = self._state_parsed(want) + else: + for w in want: + if state == "merged": + commands.extend(self._state_merged(w, have)) + elif state == "replaced": + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_parsed(self, want): + return self.get_static_routes_facts(want) + + def _state_rendered(self, want, have): + commands = [] + for w in want: + commands.extend(self.set_commands(w, {})) + return commands + + def _state_replaced(self, want, have): + """The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + delete_commands = [] + state = self._module.params["state"] + merged_commands = [] + obj_in_have = search_obj_in_list(want["vrf"], have, "vrf") + # in replaced, we check if whatever in have is in want, unlike merged. This is because we need to apply deleted on have config + if obj_in_have and obj_in_have != {"vrf": "default"}: + want_afi_list = [] + if "address_families" in want.keys(): + want_afi_list = [w["afi"] for w in want["address_families"]] + if len(want_afi_list) > 0: + for h in obj_in_have["address_families"]: + if h["afi"] in want_afi_list: + want_afi = search_obj_in_list(h["afi"], want["address_families"], "afi") + want_dest_list = [] + if "routes" in want_afi.keys(): + want_dest_list = [w["dest"] for w in want_afi["routes"]] + if len(want_dest_list) > 0: + for ro in h["routes"]: + if ro["dest"] in want_dest_list: + want_dest = search_obj_in_list( + ro["dest"], + want_afi["routes"], + "dest", + ) + want_next_hops = [] + if "next_hops" in want_dest.keys(): + want_next_hops = list(want_dest["next_hops"]) + if len(want_next_hops) > 0: + for next_hop in ro["next_hops"]: + if next_hop not in want_next_hops: + # have's next hop not in want, so delete it + delete_dict = { + "vrf": obj_in_have["vrf"], + "address_families": [ + { + "afi": h["afi"], + "routes": [ + { + "dest": ro["dest"], + "next_hops": [next_hop], + }, + ], + }, + ], + } + delete_commands.extend( + self.del_commands([delete_dict]), + ) + else: + # want has no next_hops, so delete all next_hops under that dest + if state == "overridden": + delete_dict = { + "vrf": obj_in_have["vrf"], + "address_families": [ + { + "afi": h["afi"], + "routes": [ + { + "dest": ro["dest"], + "next_hops": ro["next_hops"], + }, + ], + }, + ], + } + delete_commands.extend(self.del_commands([delete_dict])) + else: + if state == "overridden": + delete_dict = { + "vrf": obj_in_have["vrf"], + "address_families": [ + { + "afi": h["afi"], + "routes": [ + { + "dest": ro["dest"], + "next_hops": ro["next_hops"], + }, + ], + }, + ], + } + delete_commands.extend(self.del_commands([delete_dict])) + + else: + if ( + state == "overridden" + ): # want has no 'routes' key, so delete all routes under that afi + if "routes" in h.keys(): + delete_dict = { + "vrf": obj_in_have["vrf"], + "address_families": [ + { + "afi": h["afi"], + "routes": h["routes"], + }, + ], + } + delete_commands.extend(self.del_commands([delete_dict])) + else: + if ( + state == "overridden" + ): # want has 'vrf' key only. So delete all address families in it + delete_commands.extend( + self.del_commands( + [ + { + "address_families": list(obj_in_have["address_families"]), + "vrf": obj_in_have["vrf"], + }, + ], + ), + ) + final_delete_commands = [] + for d in delete_commands: + if d not in final_delete_commands: + final_delete_commands.append(d) + # if there are two afis, 'vrf context..' is added twice fom del_commands. The above code removes the redundant 'vrf context ..' + merged_commands = self.set_commands(want, have) + if merged_commands: + cmds = set(final_delete_commands).intersection(set(merged_commands)) + for c in cmds: + merged_commands.remove(c) + + # set_commands adds a 'vrf context..' line. The above code removes the redundant 'vrf context ..' + commands.extend(final_delete_commands) + commands.extend(merged_commands) + return commands + + def _state_overridden(self, want, have): + """The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + want_vrfs = [w["vrf"] for w in want] + for h in have: + if h["vrf"] not in want_vrfs and h["vrf"] != "management": + commands.extend(self._state_deleted([h], have)) + for w in want: + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_merged(self, want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return self.set_commands(want, have) + + 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 = [] + if want: + for w in want: + delete_dict = {} + obj_in_have = search_obj_in_list(w["vrf"], have, "vrf") + if obj_in_have: + if "address_families" in w.keys(): + o1 = obj_in_have["address_families"] + afi_list = [o["afi"] for o in o1] # have's afi list + for w1 in w["address_families"]: + if w1["afi"] in afi_list: + o2 = search_obj_in_list(w1["afi"], o1, "afi") + state = self._module.params["state"] + if state != "deleted": + # Deleted scope is till afi only. Part below is for use by overridden state. + if "routes" in w1.keys(): + for w2 in w1["routes"]: + o3 = search_obj_in_list( + w2["dest"], + o2["routes"], + "dest", + ) + hops = [] + if "next_hops" in w2.keys(): + for nh in w2["next_hops"]: + if nh in o3["next_hops"]: + hops.append(nh) + else: + # if next hops not given + hops = o3["next_hops"] + + delete_dict = { + "vrf": obj_in_have["vrf"], + "address_families": [ + { + "afi": w1["afi"], + "routes": [ + { + "dest": w2["dest"], + "next_hops": hops, + }, + ], + }, + ], + } + commands.extend(self.del_commands([delete_dict])) + else: + # case when only afi given for delete + delete_dict = { + "vrf": obj_in_have["vrf"], + "address_families": [ + { + "afi": o2["afi"], + "routes": o2["routes"], + }, + ], + } + commands.extend(self.del_commands([delete_dict])) + else: + commands.extend( + self.del_commands( + [ + { + "vrf": obj_in_have["vrf"], + "address_families": [o2], + }, + ], + ), + ) + else: + # only vrf given to delete + commands.extend(self.del_commands([obj_in_have])) + else: + if have: + # delete everything + del_have = [] + for h in have: + if h["vrf"] != "management": # protect management vrf + del_have.append(h) + commands = self.del_commands(del_have) + + final_delete_commands = [] + # del_commands might add 'vrf context..' twice for two routes in the same vrf. This removes it + for c in commands: + if c not in final_delete_commands: + final_delete_commands.append(c) + return final_delete_commands + + def del_commands(self, have): + commands = [] + for h in have: + if h != {"vrf": "default"}: + vrf = h["vrf"] + if "default" not in vrf: + commands.append("vrf context " + vrf) + else: + # Default static routes are configured in global context. + # "vrf context default" command throws error 9.X release onwards. + # Changing the context to global is achieved by "configure terminal" + commands.append("configure terminal") + for af in h["address_families"]: + for route in af["routes"]: + for next_hop in route["next_hops"]: + command = self.del_next_hop(af, route, next_hop) + commands.append(command.strip()) + return commands + + def del_next_hop(self, af, route, next_hop): + command = "" + if af["afi"] == "ipv4": + command = "no ip route " + route["dest"] + " " + self.add_commands(next_hop) + else: + command = "no ipv6 route " + route["dest"] + " " + self.add_commands(next_hop) + return command + + def add_commands(self, want): + command = "" + params = want.keys() + pref = vrf = ip = intf = name = tag = track = "" + if "admin_distance" in params: + pref = str(want["admin_distance"]) + " " + if "track" in params: + track = "track " + str(want["track"]) + " " + if "dest_vrf" in params: + vrf = "vrf " + str(want["dest_vrf"]) + " " + if "forward_router_address" in params: + ip = want["forward_router_address"] + " " + if "interface" in params: + intf = normalize_interface(want["interface"]) + " " + if "null0" in intf: + ip = "" + intf = "null0 " + if "route_name" in params: + name = "name " + str(want["route_name"]) + " " + if "tag" in params: + tag = "tag " + str(want["tag"]) + " " + command = intf + ip + vrf + name + tag + track + pref + if intf != "Null0 " and ip == "": + self._module.fail_json(msg="forward_router_address error") + return command.strip() + + def set_commands(self, want, have): + commands = [] + h1 = h2 = h3 = {} + want = remove_empties(want) + vrf_list = [] + if have: + vrf_list = [h["vrf"] for h in have] + if want["vrf"] in vrf_list and have != [{"vrf": "default"}]: + for x in have: + if x["vrf"] == want["vrf"]: + h1 = x # this has the 'have' dict with same vrf as want + if "address_families" in h1.keys(): + afi_list = [h["afi"] for h in h1["address_families"]] + if "address_families" in want.keys(): + for af in want["address_families"]: + if af["afi"] in afi_list: + for x in h1["address_families"]: + if x["afi"] == af["afi"]: + h2 = x # this has the have dict with same vrf and afi as want + dest_list = [h["dest"] for h in h2["routes"]] + if "routes" in af.keys(): + for ro in af["routes"]: + if ro["dest"] in dest_list: + for x in h2["routes"]: + if x["dest"] == ro["dest"]: + h3 = x # this has the have dict with same vrf, afi and dest as want + next_hop_list = list(h3["next_hops"]) + if "next_hops" in ro.keys(): + for nh in ro["next_hops"]: + if "interface" in nh.keys(): + nh["interface"] = normalize_interface( + nh["interface"], + ) + if nh not in next_hop_list: + # no match for next hop in have + commands = self.set_next_hop( + want, + h2, + nh, + ro, + commands, + ) + vrf_list.append(want["vrf"]) + else: + # no match for dest + if "next_hops" in ro.keys(): + for nh in ro["next_hops"]: + commands = self.set_next_hop( + want, + h2, + nh, + ro, + commands, + ) + else: + # no match for afi + if "routes" in af.keys(): + for ro in af["routes"]: + for nh in ro["next_hops"]: + commands = self.set_next_hop(want, af, nh, ro, commands) + else: + # no match for vrf + vrf_list.append(want["vrf"]) + for af in want["address_families"]: + for ro in af["routes"]: + for nh in ro["next_hops"]: + commands = self.set_next_hop(want, af, nh, ro, commands) + return commands + + def set_next_hop(self, want, h2, nh, ro, commands): + vrf = want["vrf"] + if h2["afi"] == "ipv4": + com = "ip route " + ro["dest"] + " " + self.add_commands(nh) + else: + com = "ipv6 route " + ro["dest"] + " " + self.add_commands(nh) + commands.append(com.strip()) + if "default" not in vrf: + string = "vrf context " + str(vrf) + else: + # Default static routes are configured in global context. + # "vrf context default" command throws error 9.X release onwards. + # Changing the context to global is achieved by "configure terminal" + string = "configure terminal" + if string not in commands: + commands.insert(0, string) + return commands + + def add_default_vrf(self, dictionary): + """ + This method is used to add 'default' vrf to the facts collected as global/default vrf + is not shown in facts. vrf key exists for all vrfs except global. + """ + for d in dictionary: + if "vrf" not in d.keys(): + d.update({"vrf": "default"}) + return dictionary diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/telemetry.py new file mode 100644 index 00000000..da3f7743 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/telemetry.py @@ -0,0 +1,593 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The nxos_telemetry 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.cisco.nxos.plugins.module_utils.network.nxos.cmdref.telemetry.telemetry import ( + TMS_DESTGROUP, + TMS_GLOBAL, + TMS_SENSORGROUP, + TMS_SUBSCRIPTION, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import NxosCmdRef +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.telemetry.telemetry import ( + get_module_params_subsection, + get_setval_path, + massage_data, + normalize_data, + remove_duplicate_commands, + remove_duplicate_context, + valiate_input, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + normalize_interface, +) + + +class Telemetry(ConfigBase): + """ + The nxos_telemetry class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["telemetry"] + + def __init__(self, module): + super(Telemetry, self).__init__(module) + + def get_telemetry_facts(self, data=None): + """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, + data=data, + ) + telemetry_facts = facts["ansible_network_resources"].get("telemetry") + if not telemetry_facts: + return {} + return telemetry_facts + + def edit_config(self, commands): + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + commands = list() + warnings = list() + + state = self._module.params["state"] + if "overridden" in state: + self._module.fail_json(msg="State <overridden> is invalid for this module.") + # When state is 'deleted', the module_params should not contain data + # under the 'config' key + if "deleted" in state and self._module.params.get("config"): + self._module.fail_json(msg="Remove config key from playbook when state is <deleted>") + + if self._module.params["config"] is None: + self._module.params["config"] = {} + # Normalize interface name. + int = self._module.params["config"].get("source_interface") + if int: + self._module.params["config"]["source_interface"] = normalize_interface(int) + + if self.state in self.ACTION_STATES: + existing_telemetry_facts = self.get_telemetry_facts() + else: + existing_telemetry_facts = [] + + if self.state in self.ACTION_STATES: + commands.extend(self.set_config(existing_telemetry_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_telemetry_facts = self.get_telemetry_facts() + + if self.state in self.ACTION_STATES: + result["before"] = existing_telemetry_facts + if result["changed"]: + result["after"] = changed_telemetry_facts + + elif self.state == "gathered": + result["gathered"] = changed_telemetry_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_tms_global_facts): + """Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + config = self._module.params["config"] + want = dict((k, v) for k, v in config.items() if v is not None) + have = existing_tms_global_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params["state"] + + # The deleted case is very simple since we purge all telemetry config + # and does not require any processing using NxosCmdRef objects. + if state == "deleted": + return self._state_deleted(want, have) + elif state == "replaced": + if want == have: + return [] + return self._state_replaced(want, have) + + # Save off module params + ALL_MP = self._module.params["config"] + + cmd_ref = {} + cmd_ref["TMS_GLOBAL"] = {} + cmd_ref["TMS_DESTGROUP"] = {} + cmd_ref["TMS_SENSORGROUP"] = {} + cmd_ref["TMS_SUBSCRIPTION"] = {} + + # Build Telemetry Global NxosCmdRef Object + cmd_ref["TMS_GLOBAL"]["ref"] = [] + self._module.params["config"] = get_module_params_subsection(ALL_MP, "TMS_GLOBAL") + cmd_ref["TMS_GLOBAL"]["ref"].append(NxosCmdRef(self._module, TMS_GLOBAL)) + ref = cmd_ref["TMS_GLOBAL"]["ref"][0] + ref.set_context() + ref.get_existing() + ref.get_playvals() + device_cache = ref.cache_existing + + def build_cmdref_objects(td): + cmd_ref[td["type"]]["ref"] = [] + saved_ids = [] + if want.get(td["name"]): + for playvals in want[td["name"]]: + valiate_input(playvals, td["name"], self._module) + if playvals["id"] in saved_ids: + continue + saved_ids.append(playvals["id"]) + resource_key = td["cmd"].format(playvals["id"]) + # Only build the NxosCmdRef object for the td['name'] module parameters. + self._module.params["config"] = get_module_params_subsection( + ALL_MP, + td["type"], + playvals["id"], + ) + cmd_ref[td["type"]]["ref"].append(NxosCmdRef(self._module, td["obj"])) + ref = cmd_ref[td["type"]]["ref"][-1] + ref.set_context([resource_key]) + if td["type"] == "TMS_SENSORGROUP" and get_setval_path(self._module): + # Sensor group path setting can contain optional values. + # Call get_setval_path helper function to process any + # optional setval keys. + ref._ref["path"]["setval"] = get_setval_path(self._module) + ref.get_existing(device_cache) + ref.get_playvals() + if td["type"] == "TMS_DESTGROUP": + normalize_data(ref) + + # Build Telemetry Destination Group NxosCmdRef Objects + td = { + "name": "destination_groups", + "type": "TMS_DESTGROUP", + "obj": TMS_DESTGROUP, + "cmd": "destination-group {0}", + } + build_cmdref_objects(td) + + # Build Telemetry Sensor Group NxosCmdRef Objects + td = { + "name": "sensor_groups", + "type": "TMS_SENSORGROUP", + "obj": TMS_SENSORGROUP, + "cmd": "sensor-group {0}", + } + build_cmdref_objects(td) + + # Build Telemetry Subscription NxosCmdRef Objects + td = { + "name": "subscriptions", + "type": "TMS_SUBSCRIPTION", + "obj": TMS_SUBSCRIPTION, + "cmd": "subscription {0}", + } + build_cmdref_objects(td) + + if state == "merged": + if want == have: + return [] + commands = self._state_merged(cmd_ref) + return commands + + @staticmethod + def _state_replaced(want, have): + """The command generator when state is replaced + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + massaged_have = massage_data(have) + massaged_want = massage_data(want) + + ref = {} + ref["tms_global"] = NxosCmdRef([], TMS_GLOBAL, ref_only=True) + ref["tms_destgroup"] = NxosCmdRef([], TMS_DESTGROUP, ref_only=True) + ref["tms_sensorgroup"] = NxosCmdRef([], TMS_SENSORGROUP, ref_only=True) + ref["tms_subscription"] = NxosCmdRef([], TMS_SUBSCRIPTION, ref_only=True) + + # Order matters for state replaced. + # First remove all subscriptions, followed by sensor-groups and destination-groups. + # Second add all destination-groups, followed by sensor-groups and subscriptions + add = { + "TMS_GLOBAL": [], + "TMS_DESTGROUP": [], + "TMS_SENSORGROUP": [], + "TMS_SUBSCRIPTION": [], + } + delete = { + "TMS_DESTGROUP": [], + "TMS_SENSORGROUP": [], + "TMS_SUBSCRIPTION": [], + } + + # Process Telemetry Global Want and Have Values + # Possible states: + # - want and have are (set) (equal: no action, not equal: replace with want) + # - want (set) have (not set) (add want) + # - want (not set) have (set) (delete have) + # - want (not set) have (not set) (no action) + # global_ctx = ref['tms_global']._ref['_template']['context'] + # property_ctx = ref['tms_global']._ref['certificate'].get('context') + # setval = ref['tms_global']._ref['certificate']['setval'] + # + all_global_properties = [ + "certificate", + "compression", + "source_interface", + "vrf", + ] + dest_profile_properties = ["compression", "source_interface", "vrf"] + dest_profile_remote_commands = [] + for property in all_global_properties: + cmd = None + global_ctx = ref["tms_global"]._ref["_template"]["context"] + property_ctx = ref["tms_global"]._ref[property].get("context") + setval = ref["tms_global"]._ref[property]["setval"] + kind = ref["tms_global"]._ref[property]["kind"] + if want.get(property) is not None: + if have.get(property) is not None: + if want.get(property) != have.get(property): + if kind == "dict": + cmd = [setval.format(**want.get(property))] + else: + cmd = [setval.format(want.get(property))] + elif have.get(property) is None: + if kind == "dict": + cmd = [setval.format(**want.get(property))] + else: + cmd = [setval.format(want.get(property))] + elif want.get(property) is None: + if have.get(property) is not None: + if kind == "dict": + cmd = ["no " + setval.format(**have.get(property))] + else: + cmd = ["no " + setval.format(have.get(property))] + if property in dest_profile_properties: + dest_profile_remote_commands.extend(cmd) + + if cmd is not None: + ctx = global_ctx + if property_ctx is not None: + ctx.extend(property_ctx) + add["TMS_GLOBAL"].extend(ctx) + add["TMS_GLOBAL"].extend(cmd) + + add["TMS_GLOBAL"] = remove_duplicate_commands(add["TMS_GLOBAL"]) + # If all destination profile commands are being removed then just + # remove the config context instead. + if len(dest_profile_remote_commands) == 3: + for item in dest_profile_remote_commands: + add["TMS_GLOBAL"].remove(item) + add["TMS_GLOBAL"].remove("destination-profile") + add["TMS_GLOBAL"].extend(["no destination-profile"]) + + # Process Telemetry destination_group, sensor_group and subscription Want and Have Values + # Possible states: + # - want (not set) have (set) (delete have) + # - want and have are (set) (equal: no action, not equal: replace with want) + # - want (set) have (not set) (add want) + # - want (not set) have (not set) (no action) + tms_resources = [ + "TMS_DESTGROUP", + "TMS_SENSORGROUP", + "TMS_SUBSCRIPTION", + ] + for resource in tms_resources: + if resource == "TMS_DESTGROUP": + name = "destination-group" + cmd_property = "destination" + global_ctx = ref["tms_destgroup"]._ref["_template"]["context"] + setval = ref["tms_destgroup"]._ref["destination"]["setval"] + want_resources = massaged_want.get("destination_groups") + have_resources = massaged_have.get("destination_groups") + if resource == "TMS_SENSORGROUP": + name = "sensor-group" + global_ctx = ref["tms_sensorgroup"]._ref["_template"]["context"] + setval = {} + setval["data_source"] = ref["tms_sensorgroup"]._ref["data_source"]["setval"] + setval["path"] = ref["tms_sensorgroup"]._ref["path"]["setval"] + want_resources = massaged_want.get("sensor_groups") + have_resources = massaged_have.get("sensor_groups") + if resource == "TMS_SUBSCRIPTION": + name = "subscription" + global_ctx = ref["tms_subscription"]._ref["_template"]["context"] + setval = {} + setval["destination_group"] = ref["tms_subscription"]._ref["destination_group"][ + "setval" + ] + setval["sensor_group"] = ref["tms_subscription"]._ref["sensor_group"]["setval"] + want_resources = massaged_want.get("subscriptions") + have_resources = massaged_have.get("subscriptions") + + if not want_resources and have_resources: + # want not and have not set so delete have + for key in have_resources.keys(): + remove_context = ["{0} {1} {2}".format("no", name, key)] + delete[resource].extend(global_ctx) + if remove_context[0] not in delete[resource]: + delete[resource].extend(remove_context) + else: + # want and have are set. + # process wants: + for want_key in want_resources.keys(): + if want_key not in have_resources.keys(): + # Want resource key not in have resource key so add it + property_ctx = ["{0} {1}".format(name, want_key)] + for item in want_resources[want_key]: + if resource == "TMS_DESTGROUP": + cmd = [setval.format(**item[cmd_property])] + add[resource].extend(global_ctx) + if property_ctx[0] not in add[resource]: + add[resource].extend(property_ctx) + add[resource].extend(cmd) + if resource == "TMS_SENSORGROUP": + cmd = {} + if item.get("data_source"): + cmd["data_source"] = [ + setval["data_source"].format(item["data_source"]), + ] + if item.get("path"): + setval["path"] = get_setval_path(item.get("path")) + cmd["path"] = [setval["path"].format(**item["path"])] + add[resource].extend(global_ctx) + if property_ctx[0] not in add[resource]: + add[resource].extend(property_ctx) + if cmd.get("data_source"): + add[resource].extend(cmd["data_source"]) + if cmd.get("path"): + add[resource].extend(cmd["path"]) + if resource == "TMS_SUBSCRIPTION": + cmd = {} + if item.get("destination_group"): + cmd["destination_group"] = [ + setval["destination_group"].format( + item["destination_group"], + ), + ] + if item.get("sensor_group"): + cmd["sensor_group"] = [ + setval["sensor_group"].format(**item["sensor_group"]), + ] + add[resource].extend(global_ctx) + if property_ctx[0] not in add[resource]: + add[resource].extend(property_ctx) + if cmd.get("destination_group"): + add[resource].extend(cmd["destination_group"]) + if cmd.get("sensor_group"): + add[resource].extend(cmd["sensor_group"]) + + elif want_key in have_resources.keys(): + # Want resource key exists in have resource keys but we need to + # inspect the individual items under the resource key + # for differences + for item in want_resources[want_key]: + if item not in have_resources[want_key]: + if item is None: + continue + # item wanted but does not exist so add it + property_ctx = ["{0} {1}".format(name, want_key)] + if resource == "TMS_DESTGROUP": + cmd = [setval.format(**item[cmd_property])] + add[resource].extend(global_ctx) + if property_ctx[0] not in add[resource]: + add[resource].extend(property_ctx) + add[resource].extend(cmd) + if resource == "TMS_SENSORGROUP": + cmd = {} + if item.get("data_source"): + cmd["data_source"] = [ + setval["data_source"].format(item["data_source"]), + ] + if item.get("path"): + setval["path"] = get_setval_path(item.get("path")) + cmd["path"] = [setval["path"].format(**item["path"])] + add[resource].extend(global_ctx) + if property_ctx[0] not in add[resource]: + add[resource].extend(property_ctx) + if cmd.get("data_source"): + add[resource].extend(cmd["data_source"]) + if cmd.get("path"): + add[resource].extend(cmd["path"]) + if resource == "TMS_SUBSCRIPTION": + cmd = {} + if item.get("destination_group"): + cmd["destination_group"] = [ + setval["destination_group"].format( + item["destination_group"], + ), + ] + if item.get("sensor_group"): + cmd["sensor_group"] = [ + setval["sensor_group"].format(**item["sensor_group"]), + ] + add[resource].extend(global_ctx) + if property_ctx[0] not in add[resource]: + add[resource].extend(property_ctx) + if cmd.get("destination_group"): + add[resource].extend(cmd["destination_group"]) + if cmd.get("sensor_group"): + add[resource].extend(cmd["sensor_group"]) + + # process haves: + for have_key in have_resources.keys(): + if have_key not in want_resources.keys(): + # Want resource key is not in have resource keys so remove it + cmd = ["no " + "{0} {1}".format(name, have_key)] + delete[resource].extend(global_ctx) + delete[resource].extend(cmd) + elif have_key in want_resources.keys(): + # Have resource key exists in want resource keys but we need to + # inspect the individual items under the resource key + # for differences + for item in have_resources[have_key]: + if item not in want_resources[have_key]: + if item is None: + continue + # have item not wanted so remove it + property_ctx = ["{0} {1}".format(name, have_key)] + if resource == "TMS_DESTGROUP": + cmd = ["no " + setval.format(**item[cmd_property])] + delete[resource].extend(global_ctx) + if property_ctx[0] not in delete[resource]: + delete[resource].extend(property_ctx) + delete[resource].extend(cmd) + if resource == "TMS_SENSORGROUP": + cmd = {} + if item.get("data_source"): + cmd["data_source"] = [ + "no " + + setval["data_source"].format(item["data_source"]), + ] + if item.get("path"): + setval["path"] = get_setval_path(item.get("path")) + cmd["path"] = [ + "no " + setval["path"].format(**item["path"]), + ] + delete[resource].extend(global_ctx) + if property_ctx[0] not in delete[resource]: + delete[resource].extend(property_ctx) + if cmd.get("data_source"): + delete[resource].extend(cmd["data_source"]) + if cmd.get("path"): + delete[resource].extend(cmd["path"]) + if resource == "TMS_SUBSCRIPTION": + cmd = {} + if item.get("destination_group"): + cmd["destination_group"] = [ + "no " + + setval["destination_group"].format( + item["destination_group"], + ), + ] + if item.get("sensor_group"): + cmd["sensor_group"] = [ + "no " + + setval["sensor_group"].format(**item["sensor_group"]), + ] + delete[resource].extend(global_ctx) + if property_ctx[0] not in delete[resource]: + delete[resource].extend(property_ctx) + if cmd.get("destination_group"): + delete[resource].extend(cmd["destination_group"]) + if cmd.get("sensor_group"): + delete[resource].extend(cmd["sensor_group"]) + + add[resource] = remove_duplicate_context(add[resource]) + delete[resource] = remove_duplicate_context(delete[resource]) + + commands.extend(delete["TMS_SUBSCRIPTION"]) + commands.extend(delete["TMS_SENSORGROUP"]) + commands.extend(delete["TMS_DESTGROUP"]) + commands.extend(add["TMS_DESTGROUP"]) + commands.extend(add["TMS_SENSORGROUP"]) + commands.extend(add["TMS_SUBSCRIPTION"]) + commands.extend(add["TMS_GLOBAL"]) + commands = remove_duplicate_context(commands) + + return commands + + @staticmethod + def _state_merged(cmd_ref): + """The command generator when state is merged + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = cmd_ref["TMS_GLOBAL"]["ref"][0].get_proposed() + + if cmd_ref["TMS_DESTGROUP"].get("ref"): + for cr in cmd_ref["TMS_DESTGROUP"]["ref"]: + commands.extend(cr.get_proposed()) + + if cmd_ref["TMS_SENSORGROUP"].get("ref"): + for cr in cmd_ref["TMS_SENSORGROUP"]["ref"]: + commands.extend(cr.get_proposed()) + + if cmd_ref["TMS_SUBSCRIPTION"].get("ref"): + for cr in cmd_ref["TMS_SUBSCRIPTION"]["ref"]: + commands.extend(cr.get_proposed()) + + return remove_duplicate_context(commands) + + @staticmethod + def _state_deleted(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 = [] + if want != have: + commands = ["no telemetry"] + return commands diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/vlans.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/vlans.py new file mode 100644 index 00000000..515618ad --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/vlans.py @@ -0,0 +1,334 @@ +# +# -*- 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 nxos_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 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 ( + dict_diff, + remove_empties, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + search_obj_in_list, +) + + +class Vlans(ConfigBase): + """ + The nxos_vlans class + """ + + gather_subset = ["min"] + + gather_network_resources = ["vlans"] + + def __init__(self, module): + super(Vlans, self).__init__(module) + + def get_platform(self): + """Wrapper method for getting platform info + This method exists solely to allow the unit test framework to mock calls. + """ + return self.facts.get("ansible_net_platform", "") + + def get_vlans_facts(self, data=None): + """Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + self.facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, + self.gather_network_resources, + data=data, + ) + vlans_facts = self.facts["ansible_network_resources"].get("vlans") + if not vlans_facts: + return [] + + return vlans_facts + + def edit_config(self, commands): + """Wrapper method for `_connection.edit_config()` + This exists solely to allow the unit test framework to mock device connection calls. + """ + return self._connection.edit_config(commands) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + commands = [] + warnings = [] + + if self.state in self.ACTION_STATES: + existing_vlans_facts = self.get_vlans_facts() + self._platform = self.get_platform() + else: + existing_vlans_facts = [] + self._platform = "" + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_vlans_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_vlans_facts = self.get_vlans_facts() + + elif self.state == "rendered": + result["rendered"] = commands + + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_vlans_facts(data=running_config) + + if self.state in self.ACTION_STATES: + result["before"] = existing_vlans_facts + if result["changed"]: + result["after"] = changed_vlans_facts + + elif self.state == "gathered": + result["gathered"] = 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 = self._module.params.get("config") or [] + have = existing_vlans_facts + resp = self.set_state(self._sanitize(want), self._sanitize(have)) + return to_list(resp) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params["state"] + if state in ("overridden", "merged", "replaced", "rendered") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format(state), + ) + + commands = list() + if state == "overridden": + commands.extend(self._state_overridden(want, have)) + elif state == "deleted": + commands.extend(self._state_deleted(want, have)) + else: + for w in want: + if state in ["merged", "rendered"]: + commands.extend(self._state_merged(w, have)) + elif state == "replaced": + commands.extend(self._state_replaced(w, have)) + + return commands + + def remove_default_states(self, obj): + """Removes non-empty but default states from the obj.""" + default_states = {"enabled": True, "state": "active", "mode": "ce"} + for k in default_states.keys(): + if obj.get(k) == default_states[k]: + obj.pop(k, None) + return obj + + def _state_replaced(self, want, have): + """The command generator when state is replaced. + Scope is limited to vlan objects defined in the playbook. + :rtype: A list + :returns: The minimum command set required to migrate the current + configuration to the desired configuration. + """ + obj_in_have = search_obj_in_list(want["vlan_id"], have, "vlan_id") + if obj_in_have: + # Diff the want and have + diff = dict_diff(want, obj_in_have) + # Remove merge items from diff; what's left will be used to + # remove states not specified in the playbook + for k in dict(set(want.items()) - set(obj_in_have.items())).keys(): + diff.pop(k, None) + else: + diff = want + + # Remove default states from resulting diff + diff = self.remove_default_states(diff) + + # merged_cmds: 'want' cmds to update 'have' states that don't match + # replaced_cmds: remaining 'have' cmds that need to be reset to default + merged_cmds = self.set_commands(want, have) + replaced_cmds = [] + if obj_in_have: + # Remaining diff items are used to reset states to default + replaced_cmds = self.del_attribs(diff) + cmds = [] + if replaced_cmds or merged_cmds: + cmds += ["vlan %s" % str(want["vlan_id"])] + cmds += merged_cmds + replaced_cmds + return cmds + + def _state_overridden(self, want, have): + """The command generator when state is overridden. + Scope includes all vlan objects on the device. + :rtype: A list + :returns: the minimum command set required to migrate the current + configuration to the desired configuration. + """ + # overridden behavior is the same as replaced except for scope. + cmds = [] + existing_vlans = [] + for h in have: + existing_vlans.append(h["vlan_id"]) + obj_in_want = search_obj_in_list(h["vlan_id"], want, "vlan_id") + if obj_in_want: + if h != obj_in_want: + replaced_cmds = self._state_replaced(obj_in_want, [h]) + if replaced_cmds: + cmds.extend(replaced_cmds) + else: + cmds.append("no vlan %s" % h["vlan_id"]) + + # Add wanted vlans that don't exist on the device yet + for w in want: + if w["vlan_id"] not in existing_vlans: + new_vlan = ["vlan %s" % w["vlan_id"]] + cmds.extend(new_vlan + self.add_commands(w)) + return cmds + + def _state_merged(self, w, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + cmds = self.set_commands(w, have) + if cmds: + cmds.insert(0, "vlan %s" % str(w["vlan_id"])) + return cmds + + 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 = [] + if want: + for w in want: + obj_in_have = search_obj_in_list(w["vlan_id"], have, "vlan_id") + if obj_in_have: + commands.append("no vlan " + str(obj_in_have["vlan_id"])) + else: + if not have: + return commands + for h in have: + commands.append("no vlan " + str(h["vlan_id"])) + return commands + + def del_attribs(self, obj): + """Returns a list of commands to reset states to default""" + commands = [] + if not obj: + return commands + default_cmds = { + "name": "no name", + "state": "no state", + "enabled": "no shutdown", + "mode": "mode ce", + "mapped_vni": "no vn-segment", + } + for k in obj: + commands.append(default_cmds[k]) + return commands + + def diff_of_dicts(self, w, obj): + diff = set(w.items()) - set(obj.items()) + diff = dict(diff) + if diff and w["vlan_id"] == obj["vlan_id"]: + diff.update({"vlan_id": w["vlan_id"]}) + return diff + + def add_commands(self, d): + commands = [] + if not d: + return commands + if "name" in d: + commands.append("name " + d["name"]) + if "state" in d: + commands.append("state " + d["state"]) + if "enabled" in d: + if d["enabled"] is True: + commands.append("no shutdown") + else: + commands.append("shutdown") + if "mode" in d: + commands.append("mode " + d["mode"]) + if "mapped_vni" in d: + commands.append("vn-segment %s" % d["mapped_vni"]) + + return commands + + def set_commands(self, w, have): + commands = [] + obj_in_have = search_obj_in_list(w["vlan_id"], have, "vlan_id") + if not obj_in_have: + commands = self.add_commands(w) + else: + diff = self.diff_of_dicts(w, obj_in_have) + commands = self.add_commands(diff) + return commands + + def _sanitize(self, vlans): + sanitized_vlans = [] + for vlan in vlans: + if not re.search("N[567][7K]", self._platform): + if "mode" in vlan: + del vlan["mode"] + sanitized_vlans.append(remove_empties(vlan)) + return sanitized_vlans diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000..053b56a9 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py @@ -0,0 +1,129 @@ +# +# -*- 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 nxos acl_interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acl_interfaces.acl_interfaces import ( + Acl_interfacesArgs, +) + + +class Acl_interfacesFacts(object): + """The nxos acl_interfaces fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Acl_interfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def get_device_data(self, connection): + return connection.get("show running-config | section ^interface") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for acl_interfaces + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + data = data.split("interface") + + resources = [] + for i in range(len(data)): + intf = data[i].split("\n") + for l in range(1, len(intf)): + if not re.search("ip(v6)?( port)? (access-group|traffic-filter)", intf[l]): + intf[l] = "" + intf = list(filter(None, intf)) + resources.append(intf) + + 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("acl_interfaces", None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {"config": objs}) + params = utils.remove_empties(params) + facts["acl_interfaces"] = params["config"] + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + config["name"] = conf[0].strip() + config["access_groups"] = [] + v4 = {"afi": "ipv4", "acls": []} + v6 = {"afi": "ipv6", "acls": []} + for c in conf[1:]: + if c: + acl4 = re.search( + r"ip(?P<port>\sport)?\saccess-group\s(?P<name>\S+)\s(?P<dir>in|out)", + c, + ) + acl6 = re.search( + r"ipv6(?P<port>\sport)?\straffic-filter\s(?P<name>\S+)\s(?P<dir>in|out)", + c, + ) + if acl4: + v4["acls"].append(self._parse(acl4)) + elif acl6: + v6["acls"].append(self._parse(acl6)) + + if len(v4["acls"]) > 0: + config["access_groups"].append(v4) + if len(v6["acls"]) > 0: + config["access_groups"].append(v6) + + return utils.remove_empties(config) + + def _parse(self, data): + return { + "name": data.group("name").strip(), + "direction": data.group("dir").strip(), + "port": True if data.group("port") else None, + } diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/acls.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/acls.py new file mode 100644 index 00000000..70ebfcdd --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/acls.py @@ -0,0 +1,327 @@ +# +# -*- 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 nxos acls fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acls.acls import ( + AclsArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + validate_ipv4_addr, + validate_ipv6_addr, +) + + +class AclsFacts(object): + """The nxos acls fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = AclsArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def get_device_data(self, connection): + data = connection.get("show running-config | section 'ip(v6)* access-list'") + if data == "{}": + return "" + return data + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for acls + :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_device_data(connection) + data = re.split("\nip", data) + v6 = [] + v4 = [] + + for i in range(len(data)): + if str(data[i]): + if "v6" in str(data[i]).split()[0]: + v6.append(data[i]) + else: + v4.append(data[i]) + + resources = [] + resources.append(v6) + resources.append(v4) + 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("acls", None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {"config": objs}) + params = utils.remove_empties(params) + facts["acls"] = params["config"] + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def get_endpoint(self, ace, pro): + ret_dict = {} + option = ace.split()[0] + if option == "any": + ret_dict.update({"any": True}) + else: + # it could be a.b.c.d or a.b.c.d/x or a.b.c.d/32 + if "/" in option: # or 'host' in option: + prefix = re.search(r"(.*)/(\d+)", option) + ip = prefix.group(1) + cidr = prefix.group(2) + if (validate_ipv4_addr(option) and int(cidr) == 32) or ( + validate_ipv6_addr(option) and int(cidr) == 128 + ): + ret_dict.update({"host": ip}) + else: + ret_dict.update({"prefix": option}) + else: + ret_dict.update({"address": option}) + wb = ace.split()[1] + ret_dict.update({"wildcard_bits": wb}) + ace = re.sub("{0}".format(wb), "", ace, 1) + ace = re.sub(option, "", ace, 1) + if pro in ["tcp", "udp"]: + keywords = ["eq", "lt", "gt", "neq", "range"] + if len(ace.split()) and ace.split()[0] in keywords: + port_protocol = {} + port_pro = re.search(r"(eq|lt|gt|neq) (\S+)", ace) + if port_pro: + port_protocol.update({port_pro.group(1): port_pro.group(2)}) + ace = re.sub(port_pro.group(1), "", ace, 1) + ace = re.sub(port_pro.group(2), "", ace, 1) + else: + limit = re.search(r"(range) (\w*) (\w*)", ace) + if limit: + port_protocol.update( + { + "range": { + "start": limit.group(2), + "end": limit.group(3), + }, + }, + ) + ace = re.sub(limit.group(2), "", ace, 1) + ace = re.sub(limit.group(3), "", ace, 1) + if port_protocol: + ret_dict.update({"port_protocol": port_protocol}) + return ace, ret_dict + + 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) + protocol_options = { + "tcp": ["fin", "established", "psh", "rst", "syn", "urg", "ack"], + "icmp": [ + "administratively_prohibited", + "alternate_address", + "conversion_error", + "dod_host_prohibited", + "dod_net_prohibited", + "echo_request", + "echo_reply", + "echo", + "general_parameter_problem", + "host_isolated", + "host_precedence_unreachable", + "host_redirect", + "host_tos_redirect", + "host_tos_unreachable", + "host_unknown", + "host_unreachable", + "information_reply", + "information_request", + "mask_reply", + "mask_request", + "mobile_redirect", + "net_redirect", + "net_tos_redirect", + "net_tos_unreachable", + "net_unreachable", + "network_unknown", + "no_room_for_option", + "option_missing", + "packet_too_big", + "parameter_problem", + "port_unreachable", + "precedence_unreachable", + "protocol_unreachable", + "unreachable", + "reassembly_timeout", + "redirect", + "router_advertisement", + "router_solicitation", + "source_quench", + "source_route_failed", + "time_exceeded", + "timestamp_reply", + "timestamp_request", + "traceroute", + "ttl_exceeded", + ], + "icmpv6": [ + "beyond_scope", + "destination_unreachable", + "echo_reply", + "echo_request", + "fragments", + "header", + "hop_limit", + "mld_query", + "mld_reduction", + "mld_report", + "mldv2", + "nd_na", + "nd_ns", + "next_header", + "no_admin", + "no_route", + "packet_too_big", + "parameter_option", + "parameter_problem", + "port_unreachable", + "reassembly_timeout", + "renum_command", + "renum_result", + "renum_seq_number", + "router_advertisement", + "router_renumbering", + "router_solicitation", + "time_exceeded", + "unreachable", + "telemetry_path", + "telemetry_queue", + ], + "igmp": ["dvmrp", "host_query", "host_report"], + } + if conf: + if "v6" in conf[0].split()[0]: + config["afi"] = "ipv6" + else: + config["afi"] = "ipv4" + config["acls"] = [] + for acl in conf: + acls = {} + if "match-local-traffic" in acl: + config["match_local_traffic"] = True + continue + acl = acl.split("\n") + acl = [a.strip() for a in acl] + acl = list(filter(None, acl)) + acls["name"] = re.match(r"(ip)?(v6)?\s?access-list (.*)", acl[0]).group(3) + acls["aces"] = [] + for ace in list(filter(None, acl[1:])): + if re.search(r"^ip(.*)access-list.*", ace): + break + ace = ace.strip() + seq = re.match(r"(\d+)", ace) + rem = "" + entry = {} + if seq: + seq = seq.group(0) + entry.update({"sequence": seq}) + ace = re.sub(seq, "", ace, 1) + grant = ace.split()[0] + if grant != "remark": + entry.update({"grant": grant}) + else: + rem = re.match(".*remark (.*)", ace).group(1) + entry.update({"remark": rem}) + + if not rem and seq: + ace = re.sub(grant, "", ace, 1) + + pro = ace.split()[0] + if pro == "icmp" and config["afi"] == "ipv6": + entry.update({"protocol": "icmpv6"}) + else: + entry.update({"protocol": pro}) + + ace = re.sub(pro, "", ace, 1) + ace, source = self.get_endpoint(ace, pro) + entry.update({"source": source}) + ace, dest = self.get_endpoint(ace, pro) + entry.update({"destination": dest}) + + dscp = re.search(r"dscp (\w*)", ace) + if dscp: + entry.update({"dscp": dscp.group(1)}) + + frag = re.search(r"fragments", ace) + if frag: + entry.update({"fragments": True}) + + prec = re.search(r"precedence (\w*)", ace) + if prec: + entry.update({"precedence": prec.group(1)}) + + log = re.search("log", ace) + if log: + entry.update({"log": True}) + + pro = entry.get("protocol", "") + if pro in ["tcp", "icmp", "icmpv6", "igmp"]: + pro_options = {} + options = {} + for option in protocol_options[pro]: + if option not in ["telemetry_path", "telemetry_queue"]: + option = re.sub("_", "-", option) + if option in ace: + if option == "echo" and ( + "echo_request" in options or "echo_reply" in options + ): + continue + elif option == "unreachable" and "port_unreachable" in options: + continue + option = re.sub("-", "_", option) + options.update({option: True}) + if options: + pro_options.update({pro: options}) + if pro_options: + entry.update({"protocol_options": pro_options}) + if entry: + acls["aces"].append(entry) + config["acls"].append(acls) + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/bfd_interfaces.py new file mode 100644 index 00000000..9d7cceac --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/bfd_interfaces.py @@ -0,0 +1,104 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# 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 + +""" +The nxos bfd_interfaces fact class +Populate the facts tree based on the current device configuration. +""" +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bfd_interfaces.bfd_interfaces import ( + Bfd_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, +) + + +class Bfd_interfacesFacts(object): + """The nxos_bfd_interfaces fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Bfd_interfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for bfd_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 = connection.get("show running-config | section '^interface|^feature bfd'") + + # Some of the bfd attributes + if "feature bfd" in data.split("\n"): + resources = data.split("interface ") + resources.pop(0) + else: + resources = [] + for resource in resources: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj and len(obj.keys()) > 1: + objs.append(obj) + + ansible_facts["ansible_network_resources"].pop("bfd_interfaces", None) + facts = {} + if objs: + facts["bfd_interfaces"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["bfd_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 + """ + config = deepcopy(spec) + + match = re.search(r"^(\S+)", conf) + intf = match.group(1) + if get_interface_type(intf) == "unknown": + return {} + config["name"] = intf + # 'bfd'/'bfd echo' do not nvgen when enabled thus set to 'enable' when None. + # 'bfd' is not supported on some platforms + config["bfd"] = utils.parse_conf_cmd_arg(conf, "bfd", "enable", "disable") or "enable" + config["echo"] = utils.parse_conf_cmd_arg(conf, "bfd echo", "enable", "disable") or "enable" + + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/bgp_address_family.py new file mode 100644 index 00000000..a1b0bc22 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/bgp_address_family.py @@ -0,0 +1,142 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos bgp_address_family 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 ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_address_family.bgp_address_family import ( + Bgp_address_familyArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_address_family import ( + Bgp_address_familyTemplate, +) + + +class Bgp_address_familyFacts(object): + """The nxos bgp_address_family facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Bgp_address_familyArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section '^router bgp'") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Bgp_address_family network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + + if not data: + data = self.get_config(connection) + + data = self._flatten_config(data) + + # parse native config using the Bgp_address_family template + bgp_address_family_parser = Bgp_address_familyTemplate(lines=data.splitlines()) + objs = bgp_address_family_parser.parse() + if objs: + nbr = [] + if "address_family" in objs: + # remove neighbor AF entries + for k, v in iteritems(objs["address_family"]): + if not k.startswith("nbr_"): + nbr.append(k) + for x in nbr: + del objs["address_family"][x] + + objs["address_family"] = list(objs["address_family"].values()) + # sort list of dictionaries + for x in objs["address_family"]: + if "aggregate_address" in x: + x["aggregate_address"] = sorted( + x["aggregate_address"], + key=lambda k, s="prefix": k[s], + ) + if "networks" in x: + x["networks"] = sorted(x["networks"], key=lambda k, s="prefix": k[s]) + if "redistribute" in x: + x["redistribute"] = sorted( + x["redistribute"], + key=lambda k: (k.get("id", -1), k["protocol"]), + ) + objs["address_family"] = sorted( + objs["address_family"], + key=lambda k: ( + k.get("afi", ""), + k.get("safi", ""), + k.get("vrf", ""), + ), + ) + + ansible_facts["ansible_network_resources"].pop("bgp_address_family", None) + + params = utils.remove_empties(utils.validate_config(self.argument_spec, {"config": objs})) + + facts["bgp_address_family"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts + + def _flatten_config(self, data): + """Flatten contexts in the BGP + running-config for easier parsing. + :param obj: dict + :returns: flattened running config + """ + data = data.split("\n") + in_vrf_cxt = False + in_nbr_cxt = False + cur_vrf = {} + + for x in data: + cur_indent = len(x) - len(x.lstrip()) + if x.strip().startswith("vrf"): + in_vrf_cxt = True + in_nbr_cxt = False + cur_vrf["vrf"] = x + cur_vrf["indent"] = cur_indent + elif cur_vrf and (cur_indent <= cur_vrf["indent"]): + in_vrf_cxt = False + elif x.strip().startswith("neighbor"): + # we entered a neighbor context which + # also has address-family lines + in_nbr_cxt = True + nbr = x + elif x.strip().startswith("address-family"): + if in_vrf_cxt or in_nbr_cxt: + prepend = "" + if in_vrf_cxt: + prepend += cur_vrf["vrf"] + if in_nbr_cxt: + if in_vrf_cxt: + nbr = " " + nbr.strip() + prepend += nbr + data[data.index(x)] = prepend + " " + x.strip() + + return "\n".join(data) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/bgp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/bgp_global.py new file mode 100644 index 00000000..621e499f --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/bgp_global.py @@ -0,0 +1,130 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos bgp_global fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_global.bgp_global import ( + Bgp_globalArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_global import ( + Bgp_globalTemplate, +) + + +class Bgp_globalFacts(object): + """The nxos bgp_global facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Bgp_globalArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section '^router bgp'") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Bgp_global network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + + if not data: + data = self.get_config(connection) + + data = self._flatten_config(data) + + # parse native config using the Bgp_global template + bgp_global_parser = Bgp_globalTemplate(lines=data.splitlines(), module=self._module) + obj = bgp_global_parser.parse() + + vrfs = obj.get("vrfs", {}) + + # move global vals to their correct position in facts tree + # this is only needed for keys that are valid for both global + # and VRF contexts + global_vals = vrfs.pop("vrf_", {}) + for key, value in iteritems(global_vals): + obj[key] = value + + # transform vrfs into a list + if vrfs: + obj["vrfs"] = sorted(list(obj["vrfs"].values()), key=lambda k, sk="vrf": k[sk]) + for vrf in obj["vrfs"]: + self._post_parse(vrf) + + self._post_parse(obj) + + obj = utils.remove_empties(obj) + + ansible_facts["ansible_network_resources"].pop("bgp_global", None) + params = utils.remove_empties( + bgp_global_parser.validate_config(self.argument_spec, {"config": obj}, redact=True), + ) + + facts["bgp_global"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts + + def _flatten_config(self, data): + """Flatten neighbor contexts in + the running-config for easier parsing. + :param obj: dict + :returns: flattened running config + """ + data = data.split("\n") + in_nbr_cxt = False + cur_nbr = {} + + for x in data: + cur_indent = len(x) - len(x.lstrip()) + if x.strip().startswith("neighbor"): + in_nbr_cxt = True + cur_nbr["nbr"] = x + cur_nbr["indent"] = cur_indent + elif cur_nbr and (cur_indent <= cur_nbr["indent"]): + in_nbr_cxt = False + elif in_nbr_cxt: + data[data.index(x)] = cur_nbr["nbr"] + " " + x.strip() + + return "\n".join(data) + + def _post_parse(self, obj): + """Converts the intermediate data structure + to valid format as per argspec. + :param obj: dict + """ + conf_peers = obj.get("confederation", {}).get("peers") + if conf_peers: + obj["confederation"]["peers"] = conf_peers.split() + obj["confederation"]["peers"].sort() + + neighbors = obj.get("neighbors", {}) + if neighbors: + obj["neighbors"] = sorted( + list(neighbors.values()), + key=lambda k, sk="neighbor_address": k[sk], + ) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/bgp_neighbor_address_family.py new file mode 100644 index 00000000..e26c1826 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/bgp_neighbor_address_family.py @@ -0,0 +1,133 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos bgp_neighbor_address_family 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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_neighbor_address_family.bgp_neighbor_address_family import ( + Bgp_neighbor_address_familyArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_neighbor_address_family import ( + Bgp_neighbor_address_familyTemplate, +) + + +class Bgp_neighbor_address_familyFacts(object): + """The nxos bgp_neighbor_address_family facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Bgp_neighbor_address_familyArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section '^router bgp'") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Bgp_neighbor_address_family network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = {} + + if not data: + data = self.get_config(connection) + + data = self._flatten_config(data) + + # parse native config using the Bgp_neighbor_address_family template + bgp_neighbor_address_family_parser = Bgp_neighbor_address_familyTemplate(lines=data) + objs = bgp_neighbor_address_family_parser.parse() + + if objs: + top_lvl_nbrs = objs.get("vrfs", {}).pop("vrf_", {}) + objs["neighbors"] = self._post_parse(top_lvl_nbrs).get("neighbors", []) + + if "vrfs" in objs: + for vrf in objs["vrfs"].values(): + vrf["neighbors"] = self._post_parse(vrf)["neighbors"] + objs["vrfs"] = list(objs["vrfs"].values()) + + ansible_facts["ansible_network_resources"].pop("bgp_neighbor_address_family", None) + + params = utils.remove_empties(utils.validate_config(self.argument_spec, {"config": objs})) + + facts["bgp_neighbor_address_family"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts + + def _post_parse(self, data): + if "neighbors" in data: + data["neighbors"] = sorted( + list(data["neighbors"].values()), + key=lambda k, s="neighbor_address": k[s], + ) + for nbr in data["neighbors"]: + nbr["address_family"] = sorted( + list(nbr["address_family"].values()), + key=lambda k: (k["afi"], k.get("safi", "")), + ) + return data + + def _flatten_config(self, data): + """Flatten contexts in the BGP + running-config for easier parsing. + Only neighbor AF contexts are returned. + :param data: str + :returns: flattened running config + """ + data = data.split("\n") + nbr_af_cxt = [] + context = "" + cur_vrf = "" + cur_nbr_indent = None + in_nbr_cxt = False + in_af = False + + # this is the "router bgp <asn>" line + nbr_af_cxt.append(data[0]) + for x in data: + cur_indent = len(x) - len(x.lstrip()) + x = x.strip() + if x.startswith("vrf"): + cur_vrf = x + " " + in_nbr_cxt = False + elif x.startswith("neighbor"): + in_nbr_cxt = True + in_af = False + cur_nbr_indent = cur_indent + context = x + if cur_vrf: + context = cur_vrf + context + elif in_nbr_cxt and cur_indent > cur_nbr_indent: + if x.startswith("address-family"): + in_af = True + x = context + " " + x + if in_af: + nbr_af_cxt.append(x) + else: + in_nbr_cxt = False + + return nbr_af_cxt diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/facts.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/facts.py new file mode 100644 index 00000000..b70cb590 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/facts.py @@ -0,0 +1,188 @@ +# +# -*- 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) +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type +""" +The facts class for nxos +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import ( + FactsBase, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.acl_interfaces.acl_interfaces import ( + Acl_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.acls.acls import ( + AclsFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.bfd_interfaces.bfd_interfaces import ( + Bfd_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.bgp_address_family.bgp_address_family import ( + Bgp_address_familyFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.bgp_global.bgp_global import ( + Bgp_globalFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.bgp_neighbor_address_family.bgp_neighbor_address_family import ( + Bgp_neighbor_address_familyFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.hostname.hostname import ( + HostnameFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.hsrp_interfaces.hsrp_interfaces import ( + Hsrp_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.interfaces.interfaces import ( + InterfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.l2_interfaces.l2_interfaces import ( + L2_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.l3_interfaces.l3_interfaces import ( + L3_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lacp.lacp import ( + LacpFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lacp_interfaces.lacp_interfaces import ( + Lacp_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lag_interfaces.lag_interfaces import ( + Lag_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.legacy.base import ( + Config, + Default, + Features, + Hardware, + Interfaces, + Legacy, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lldp_global.lldp_global import ( + Lldp_globalFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lldp_interfaces.lldp_interfaces import ( + Lldp_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.logging_global.logging_global import ( + Logging_globalFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.ntp_global.ntp_global import ( + Ntp_globalFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.ospf_interfaces.ospf_interfaces import ( + Ospf_interfacesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.ospfv2.ospfv2 import ( + Ospfv2Facts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.ospfv3.ospfv3 import ( + Ospfv3Facts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.prefix_lists.prefix_lists import ( + Prefix_listsFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.route_maps.route_maps import ( + Route_mapsFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.snmp_server.snmp_server import ( + Snmp_serverFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.static_routes.static_routes import ( + Static_routesFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.telemetry.telemetry import ( + TelemetryFacts, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vlans.vlans import ( + VlansFacts, +) + + +FACT_LEGACY_SUBSETS = dict( + default=Default, + legacy=Legacy, + hardware=Hardware, + interfaces=Interfaces, + config=Config, + features=Features, +) +NX_FACT_RESOURCE_SUBSETS = dict( + bfd_interfaces=Bfd_interfacesFacts, + hsrp_interfaces=Hsrp_interfacesFacts, + lag_interfaces=Lag_interfacesFacts, + lldp_global=Lldp_globalFacts, + telemetry=TelemetryFacts, + vlans=VlansFacts, + lacp=LacpFacts, + lacp_interfaces=Lacp_interfacesFacts, + interfaces=InterfacesFacts, + l3_interfaces=L3_interfacesFacts, + l2_interfaces=L2_interfacesFacts, + lldp_interfaces=Lldp_interfacesFacts, + acl_interfaces=Acl_interfacesFacts, + acls=AclsFacts, + static_routes=Static_routesFacts, + ospfv2=Ospfv2Facts, + ospfv3=Ospfv3Facts, + ospf_interfaces=Ospf_interfacesFacts, + bgp_global=Bgp_globalFacts, + bgp_address_family=Bgp_address_familyFacts, + bgp_neighbor_address_family=Bgp_neighbor_address_familyFacts, + route_maps=Route_mapsFacts, + prefix_lists=Prefix_listsFacts, + logging_global=Logging_globalFacts, + ntp_global=Ntp_globalFacts, + snmp_server=Snmp_serverFacts, + hostname=HostnameFacts, +) +MDS_FACT_RESOURCE_SUBSETS = dict( + logging_global=Logging_globalFacts, + ntp_global=Ntp_globalFacts, + snmp_server=Snmp_serverFacts, +) + + +class Facts(FactsBase): + """The fact class for nxos""" + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + + def __init__(self, module, chassis_type="nexus"): + super(Facts, self).__init__(module) + self.chassis_type = chassis_type + + def get_resource_subsets(self): + """Return facts resource subsets based on + target device model. + """ + facts_resource_subsets = NX_FACT_RESOURCE_SUBSETS + if self.chassis_type == "mds": + facts_resource_subsets = MDS_FACT_RESOURCE_SUBSETS + return facts_resource_subsets + + def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None): + """Collect the facts for nxos + :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 + """ + VALID_RESOURCE_SUBSETS = self.get_resource_subsets() + + if frozenset(VALID_RESOURCE_SUBSETS.keys()): + self.get_network_resources_facts(VALID_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/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/hostname.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/hostname.py new file mode 100644 index 00000000..981648c0 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/hostname.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# 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 + +""" +The nxos hostname 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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hostname.hostname import ( + HostnameArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.hostname import ( + HostnameTemplate, +) + + +class HostnameFacts(object): + """The nxos hostname facts class""" + + def __init__(self, module): + self._module = module + self.argument_spec = HostnameArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section ^hostname") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Hostname network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + + if not data: + data = self.get_config(connection) + + # parse native config using the Hostname template + hostname_parser = HostnameTemplate(lines=data.splitlines(), module=self._module) + objs = hostname_parser.parse() + + ansible_facts["ansible_network_resources"].pop("hostname", None) + + params = utils.remove_empties( + hostname_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), + ) + + facts["hostname"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/hsrp_interfaces.py new file mode 100644 index 00000000..d12e3223 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/hsrp_interfaces.py @@ -0,0 +1,96 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# 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 + +""" +The nxos hsrp_interfaces fact class +Populate the facts tree based on the current device configuration. +""" +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hsrp_interfaces.hsrp_interfaces import ( + Hsrp_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, +) + + +class Hsrp_interfacesFacts(object): + """The nxos hsrp_interfaces fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Hsrp_interfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for hsrp_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 = connection.get("show running-config | section ^interface") + + resources = data.split("interface ") + for resource in resources: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj and len(obj.keys()) > 1: + objs.append(obj) + + ansible_facts["ansible_network_resources"].pop("hsrp_interfaces", None) + facts = {} + if objs: + facts["hsrp_interfaces"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["hsrp_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 + """ + config = deepcopy(spec) + + match = re.search(r"^(\S+)", conf) + intf = match.group(1) + if get_interface_type(intf) == "unknown": + return {} + config["name"] = intf + config["bfd"] = utils.parse_conf_cmd_arg(conf, "hsrp bfd", "enable", "disable") + + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/interfaces.py new file mode 100644 index 00000000..92592b07 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/interfaces.py @@ -0,0 +1,110 @@ +# +# -*- 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)#!/usr/bin/python +""" +The nxos 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.cisco.nxos.plugins.module_utils.network.nxos.argspec.interfaces.interfaces import ( + InterfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, +) + + +class InterfacesFacts(object): + """The nxos interfaces fact class""" + + 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 populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for interfaces + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + if not data: + data = connection.get("show running-config | section ^interface") + + config = ("\n" + data).split("\ninterface ") + for conf in config: + conf = conf.strip() + if conf: + obj = self.render_config(self.generated_spec, conf) + if obj: + objs.append(obj) + + ansible_facts["ansible_network_resources"].pop("interfaces", None) + facts = {} + facts["interfaces"] = [] + if objs: + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["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 + """ + config = deepcopy(spec) + + match = re.search(r"^(\S+)", conf) + intf = match.group(1) + if get_interface_type(intf) == "unknown": + return {} + config["name"] = intf + config["description"] = utils.parse_conf_arg(conf, "description") + config["speed"] = utils.parse_conf_arg(conf, "speed") + config["mtu"] = utils.parse_conf_arg(conf, "mtu") + config["duplex"] = utils.parse_conf_arg(conf, "duplex") + config["mode"] = utils.parse_conf_cmd_arg(conf, "switchport", "layer2", "layer3") + + config["enabled"] = utils.parse_conf_cmd_arg(conf, "shutdown", False, True) + + config["fabric_forwarding_anycast_gateway"] = utils.parse_conf_cmd_arg( + conf, + "fabric forwarding mode anycast-gateway", + True, + ) + config["ip_forward"] = utils.parse_conf_cmd_arg(conf, "ip forward", True) + + interfaces_cfg = utils.remove_empties(config) + return interfaces_cfg diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/l2_interfaces.py new file mode 100644 index 00000000..cf55552b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/l2_interfaces.py @@ -0,0 +1,104 @@ +# +# -*- 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)#!/usr/bin/python +""" +The nxos 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.cisco.nxos.plugins.module_utils.network.nxos.argspec.l2_interfaces.l2_interfaces import ( + L2_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, +) + + +class L2_interfacesFacts(object): + """The nxos 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 populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for l2_interfaces + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + if not data: + data = connection.get("show running-config | section ^interface") + + config = ("\n" + data).split("\ninterface ") + for conf in config: + conf = conf.strip() + if conf: + obj = self.render_config(self.generated_spec, conf) + 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 + """ + config = deepcopy(spec) + + match = re.search(r"^(\S+)", conf) + intf = match.group(1) + if get_interface_type(intf) == "unknown": + return {} + + config["name"] = intf + config["mode"] = utils.parse_conf_arg(conf, "switchport mode") + config["ip_forward"] = utils.parse_conf_arg(conf, "ip forward") + config["access"]["vlan"] = utils.parse_conf_arg(conf, "switchport access vlan") + config["trunk"]["allowed_vlans"] = utils.parse_conf_arg( + conf, + "switchport trunk allowed vlan", + ) + config["trunk"]["native_vlan"] = utils.parse_conf_arg(conf, "switchport trunk native vlan") + + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/l3_interfaces.py new file mode 100644 index 00000000..ad36e17c --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/l3_interfaces.py @@ -0,0 +1,135 @@ +# +# -*- 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)#!/usr/bin/python +""" +The nxos 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.cisco.nxos.plugins.module_utils.network.nxos.argspec.l3_interfaces.l3_interfaces import ( + L3_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, +) + + +class L3_interfacesFacts(object): + """The nxos l3_interfaces fact class""" + + 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 populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for l3_interfaces + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + if not data: + data = connection.get("show running-config | section ^interface") + + config = ("\n" + data).split("\ninterface ") + for conf in config: + conf = conf.strip() + if conf: + obj = self.render_config(self.generated_spec, conf) + if obj: + objs.append(obj) + + ansible_facts["ansible_network_resources"].pop("l3_interfaces", None) + facts = {} + if objs: + facts["l3_interfaces"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["l3_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 + """ + config = deepcopy(spec) + match = re.search(r"^(\S+)", conf) + intf = match.group(1) + if get_interface_type(intf) == "unknown": + return {} + config["name"] = intf + config["dot1q"] = utils.parse_conf_arg(conf, "encapsulation dot1[qQ]") + config["redirects"] = utils.parse_conf_cmd_arg(conf, "no ip redirects", False, True) + config["ipv6_redirects"] = utils.parse_conf_cmd_arg(conf, "no ipv6 redirects", False, True) + config["unreachables"] = utils.parse_conf_cmd_arg(conf, "ip unreachables", True, False) + config["evpn_multisite_tracking"] = utils.parse_conf_arg(conf, "evpn multisite") + ipv4_match = re.compile(r"\n ip address (.*)") + matches = ipv4_match.findall(conf) + if matches: + if matches[0]: + config["ipv4"] = [] + for m in matches: + ipv4_conf = m.split() + addr = ipv4_conf[0] + if addr: + config_dict = {"address": addr} + if len(ipv4_conf) > 1: + d = ipv4_conf[1] + if d == "secondary": + config_dict.update({"secondary": True}) + if len(ipv4_conf) == 4: + if ipv4_conf[2] == "tag": + config_dict.update({"tag": int(ipv4_conf[-1])}) + elif d == "tag": + config_dict.update({"tag": int(ipv4_conf[-1])}) + config["ipv4"].append(config_dict) + + ipv6_match = re.compile(r"\n ipv6 address (.*)") + matches = ipv6_match.findall(conf) + if matches: + if matches[0]: + config["ipv6"] = [] + for m in matches: + ipv6_conf = m.split() + addr = ipv6_conf[0] + if addr: + config_dict = {"address": addr} + if len(ipv6_conf) > 1: + d = ipv6_conf[1] + if d == "tag": + config_dict.update({"tag": int(ipv6_conf[-1])}) + config["ipv6"].append(config_dict) + + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/lacp.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/lacp.py new file mode 100644 index 00000000..51b1413d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/lacp.py @@ -0,0 +1,89 @@ +# +# -*- 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 nxos lacp 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.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp.lacp import ( + LacpArgs, +) + + +class LacpFacts(object): + """The nxos lacp fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = LacpArgs.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 lacp + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + data = connection.get("show running-config | include lacp") + resources = data.strip() + objs = self.render_config(self.generated_spec, resources) + ansible_facts["ansible_network_resources"].pop("lacp", None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {"config": objs}) + facts["lacp"] = utils.remove_empties(params["config"]) + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + + p_match = re.search(r"lacp system-priority (\d+)", conf, re.M) + if p_match: + config["system"]["priority"] = p_match.group(1) + + a_match = re.search(r"lacp system-mac (\S+)", conf, re.M) + if a_match: + address = a_match.group(1) + config["system"]["mac"]["address"] = address + r_match = re.search(r"lacp system-mac {0} role (\S+)".format(address), conf, re.M) + if r_match: + config["system"]["mac"]["role"] = r_match.group(1) + + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 00000000..c60d8abd --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,115 @@ +# +# -*- 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 nxos lacp_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.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp_interfaces.lacp_interfaces import ( + Lacp_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, +) + + +class Lacp_interfacesFacts(object): + """The nxos lacp_interfaces fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Lacp_interfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for lacp_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 = connection.get("show running-config | section ^interface") + + resources = ("\n" + data).split("\ninterface ") + for resource in resources: + if resource and re.search(r"lacp", resource): + obj = self.render_config(self.generated_spec, resource) + if obj and len(obj.keys()) > 1: + objs.append(obj) + + ansible_facts["ansible_network_resources"].pop("lacp_interfaces", None) + facts = {} + if objs: + facts["lacp_interfaces"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["lacp_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 + """ + config = deepcopy(spec) + + match = re.search(r"^(\S+)", conf) + intf = match.group(1) + if get_interface_type(intf) == "unknown": + return {} + config["name"] = intf + config["port_priority"] = utils.parse_conf_arg(conf, "lacp port-priority") + config["rate"] = utils.parse_conf_arg(conf, "lacp rate") + config["mode"] = utils.parse_conf_arg(conf, "lacp mode") + suspend_individual = re.search(r"no lacp suspend-individual", conf) + if suspend_individual: + config["suspend_individual"] = False + max_links = utils.parse_conf_arg(conf, "lacp max-bundle") + if max_links: + config["links"]["max"] = max_links + min_links = utils.parse_conf_arg(conf, "lacp min-links") + if min_links: + config["links"]["min"] = min_links + graceful = re.search(r"no lacp graceful-convergence", conf) + if graceful: + config["convergence"]["graceful"] = False + vpc = re.search(r"lacp vpc-convergence", conf) + if vpc: + config["convergence"]["vpc"] = True + + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000..b94c57e2 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py @@ -0,0 +1,104 @@ +# +# -*- 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)#!/usr/bin/python +""" +The nxos 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.cisco.nxos.plugins.module_utils.network.nxos.argspec.lag_interfaces.lag_interfaces import ( + Lag_interfacesArgs, +) + + +class Lag_interfacesFacts(object): + """The nxos 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 populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for lag_interfaces + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + if not data: + data = connection.get("show running-config | section ^interface") + + objs = self.render_config(self.generated_spec, data, connection) + + ansible_facts["ansible_network_resources"].pop("lag_interfaces", None) + facts = {} + if objs: + facts["lag_interfaces"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["lag_interfaces"].append(utils.remove_empties(cfg)) + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def render_config(self, spec, conf, connection): + """ + 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 + """ + result = [] + match = re.findall(r"interface (port-channel\d+)", conf) + + for item in match: + result.append({"name": item, "members": []}) + + for intf in conf.split("interface "): + member = {} + match_intf = re.search(r"(port-channel|Ethernet)(\S+)", intf) + if match_intf: + member["member"] = match_intf.group(0) + + match_line = re.search( + r"channel-group\s(?P<port_channel>\d+)(\smode\s(?P<mode>on|active|passive))?", + intf, + ) + if match_line: + member.update(match_line.groupdict()) + + if member and member.get("port_channel", None): + port_channel = "port-channel{0}".format(member.pop("port_channel")) + for x in result: + if x["name"] == port_channel: + x["members"].append(utils.remove_empties(member)) + + return result diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/base.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/base.py new file mode 100644 index 00000000..2244e222 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/base.py @@ -0,0 +1,793 @@ +# -*- 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) +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import platform +import re + +from ansible.module_utils.six import iteritems + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + get_config, + run_commands, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, + normalize_interface, +) + + +g_config = None + + +class FactsBase(object): + def __init__(self, module): + self.module = module + self.warnings = list() + self.facts = dict() + self.capabilities = get_capabilities(self.module) + + def populate(self): + pass + + def run(self, command, output="text"): + command_string = command + command = {"command": command, "output": output} + resp = run_commands(self.module, [command], check_rc="retry_json") + try: + return resp[0] + except IndexError: + self.warnings.append( + "command %s failed, facts for this command will not be populated" % command_string, + ) + return None + + def get_config(self): + global g_config + if not g_config: + g_config = get_config(self.module) + return g_config + + def transform_dict(self, data, keymap): + transform = dict() + for key, fact in keymap: + if key in data: + transform[fact] = data[key] + return transform + + def transform_iterable(self, iterable, keymap): + for item in iterable: + yield self.transform_dict(item, keymap) + + +class Default(FactsBase): + def populate(self): + data = None + data = self.run("show version") + + if data: + self.facts["serialnum"] = self.parse_serialnum(data) + + data = self.run("show license host-id") + if data: + self.facts["license_hostid"] = self.parse_license_hostid(data) + + self.facts.update(self.platform_facts()) + + def parse_serialnum(self, data): + match = re.search(r"Processor Board ID\s*(\S+)", data, re.M) + if match: + return match.group(1) + + def platform_facts(self): + platform_facts = {} + + resp = self.capabilities + device_info = resp["device_info"] + + platform_facts["system"] = device_info["network_os"] + + for item in ("model", "image", "version", "platform", "hostname"): + val = device_info.get("network_os_%s" % item) + if val: + platform_facts[item] = val + + platform_facts["api"] = resp["network_api"] + platform_facts["python_version"] = platform.python_version() + + return platform_facts + + def parse_license_hostid(self, data): + match = re.search(r"License hostid: VDH=(.+)$", data, re.M) + if match: + return match.group(1) + + +class Config(FactsBase): + def populate(self): + super(Config, self).populate() + self.facts["config"] = self.get_config() + + +class Features(FactsBase): + def populate(self): + super(Features, self).populate() + data = self.get_config() + + if data: + features = [] + for line in data.splitlines(): + if line.startswith("feature"): + features.append(line.replace("feature", "").strip()) + + self.facts["features_enabled"] = features + + +class Hardware(FactsBase): + def populate(self): + data = self.run("dir") + if data: + self.facts["filesystems"] = self.parse_filesystems(data) + + data = None + data = self.run("show system resources", output="json") + + if data: + if isinstance(data, dict): + self.facts["memtotal_mb"] = int(data["memory_usage_total"]) / 1024 + self.facts["memfree_mb"] = int(data["memory_usage_free"]) / 1024 + else: + self.facts["memtotal_mb"] = self.parse_memtotal_mb(data) + self.facts["memfree_mb"] = self.parse_memfree_mb(data) + + def parse_filesystems(self, data): + return re.findall(r"^Usage for (\S+)//", data, re.M) + + def parse_memtotal_mb(self, data): + match = re.search(r"(\S+)K(\s+|)total", data, re.M) + if match: + memtotal = match.group(1) + return int(memtotal) / 1024 + + def parse_memfree_mb(self, data): + match = re.search(r"(\S+)K(\s+|)free", data, re.M) + if match: + memfree = match.group(1) + return int(memfree) / 1024 + + +class Interfaces(FactsBase): + INTERFACE_MAP = frozenset( + [ + ("state", "state"), + ("desc", "description"), + ("eth_bw", "bandwidth"), + ("eth_duplex", "duplex"), + ("eth_speed", "speed"), + ("eth_mode", "mode"), + ("eth_hw_addr", "macaddress"), + ("eth_mtu", "mtu"), + ("eth_hw_desc", "type"), + ], + ) + + INTERFACE_SVI_MAP = frozenset( + [ + ("svi_line_proto", "state"), + ("svi_bw", "bandwidth"), + ("svi_mac", "macaddress"), + ("svi_mtu", "mtu"), + ("type", "type"), + ], + ) + + INTERFACE_IPV4_MAP = frozenset([("eth_ip_addr", "address"), ("eth_ip_mask", "masklen")]) + + INTERFACE_SVI_IPV4_MAP = frozenset([("svi_ip_addr", "address"), ("svi_ip_mask", "masklen")]) + + INTERFACE_IPV6_MAP = frozenset([("addr", "address"), ("prefix", "subnet")]) + + def ipv6_structure_op_supported(self): + data = self.capabilities + if data: + nxos_os_version = data["device_info"]["network_os_version"] + unsupported_versions = ["I2", "F1", "A8"] + for ver in unsupported_versions: + if ver in nxos_os_version: + return False + return True + + def populate(self): + self.facts["all_ipv4_addresses"] = list() + self.facts["all_ipv6_addresses"] = list() + self.facts["neighbors"] = {} + data = None + + data = self.run("show interface", output="json") + + if data: + if isinstance(data, dict): + self.facts["interfaces"] = self.populate_structured_interfaces(data) + else: + interfaces = self.parse_interfaces(data) + self.facts["interfaces"] = self.populate_interfaces(interfaces) + + if self.ipv6_structure_op_supported(): + data = self.run("show ipv6 interface", output="json") + else: + data = None + if data: + if isinstance(data, dict): + self.populate_structured_ipv6_interfaces(data) + else: + interfaces = self.parse_interfaces(data) + self.populate_ipv6_interfaces(interfaces) + + data = self.run("show lldp neighbors", output="json") + if data: + if isinstance(data, dict): + self.facts["neighbors"].update(self.populate_structured_neighbors_lldp(data)) + else: + self.facts["neighbors"].update(self.populate_neighbors(data)) + + data = self.run("show cdp neighbors detail", output="json") + if data: + if isinstance(data, dict): + self.facts["neighbors"].update(self.populate_structured_neighbors_cdp(data)) + else: + self.facts["neighbors"].update(self.populate_neighbors_cdp(data)) + + self.facts["neighbors"].pop(None, None) # Remove null key + + def populate_structured_interfaces(self, data): + interfaces = dict() + data = data["TABLE_interface"]["ROW_interface"] + + if isinstance(data, dict): + data = [data] + + for item in data: + name = item["interface"] + + intf = dict() + if "type" in item: + intf.update(self.transform_dict(item, self.INTERFACE_SVI_MAP)) + else: + intf.update(self.transform_dict(item, self.INTERFACE_MAP)) + + if "eth_ip_addr" in item: + intf["ipv4"] = self.transform_dict(item, self.INTERFACE_IPV4_MAP) + self.facts["all_ipv4_addresses"].append(item["eth_ip_addr"]) + + if "svi_ip_addr" in item: + intf["ipv4"] = self.transform_dict(item, self.INTERFACE_SVI_IPV4_MAP) + self.facts["all_ipv4_addresses"].append(item["svi_ip_addr"]) + + interfaces[name] = intf + + return interfaces + + def populate_structured_ipv6_interfaces(self, data): + try: + data = data["TABLE_intf"] + if data: + if isinstance(data, dict): + data = [data] + for item in data: + name = item["ROW_intf"]["intf-name"] + intf = self.facts["interfaces"][name] + intf["ipv6"] = self.transform_dict(item, self.INTERFACE_IPV6_MAP) + try: + addr = item["ROW_intf"]["addr"] + except KeyError: + addr = item["ROW_intf"]["TABLE_addr"]["ROW_addr"]["addr"] + self.facts["all_ipv6_addresses"].append(addr) + else: + return "" + except TypeError: + return "" + + def populate_structured_neighbors_lldp(self, data): + objects = dict() + data = data["TABLE_nbor"]["ROW_nbor"] + + if isinstance(data, dict): + data = [data] + + for item in data: + local_intf = normalize_interface(item["l_port_id"]) + objects[local_intf] = list() + nbor = dict() + nbor["port"] = item["port_id"] + nbor["host"] = nbor["sysname"] = item["chassis_id"] + objects[local_intf].append(nbor) + + return objects + + def populate_structured_neighbors_cdp(self, data): + objects = dict() + data = data["TABLE_cdp_neighbor_detail_info"]["ROW_cdp_neighbor_detail_info"] + + if isinstance(data, dict): + data = [data] + + for item in data: + if "intf_id" in item: + local_intf = item["intf_id"] + else: + # in some N7Ks the key has been renamed + local_intf = item["interface"] + objects[local_intf] = list() + nbor = dict() + nbor["port"] = item["port_id"] + nbor["host"] = nbor["sysname"] = item["device_id"] + objects[local_intf].append(nbor) + + return objects + + def parse_interfaces(self, data): + parsed = dict() + key = "" + for line in data.split("\n"): + if len(line) == 0: + continue + elif line.startswith("admin") or line[0] == " ": + parsed[key] += "\n%s" % line + else: + match = re.match(r"^(\S+)", line) + if match: + key = match.group(1) + if not key.startswith("admin") or not key.startswith("IPv6 Interface"): + parsed[key] = line + return parsed + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + if get_interface_type(key) == "svi": + intf["state"] = self.parse_state(key, value, intf_type="svi") + intf["macaddress"] = self.parse_macaddress(value, intf_type="svi") + intf["mtu"] = self.parse_mtu(value, intf_type="svi") + intf["bandwidth"] = self.parse_bandwidth(value, intf_type="svi") + intf["type"] = self.parse_type(value, intf_type="svi") + if "Internet Address" in value: + intf["ipv4"] = self.parse_ipv4_address(value, intf_type="svi") + facts[key] = intf + else: + intf["state"] = self.parse_state(key, value) + intf["description"] = self.parse_description(value) + intf["macaddress"] = self.parse_macaddress(value) + intf["mode"] = self.parse_mode(value) + intf["mtu"] = self.parse_mtu(value) + intf["bandwidth"] = self.parse_bandwidth(value) + intf["duplex"] = self.parse_duplex(value) + intf["speed"] = self.parse_speed(value) + intf["type"] = self.parse_type(value) + if "Internet Address" in value: + intf["ipv4"] = self.parse_ipv4_address(value) + facts[key] = intf + + return facts + + def parse_state(self, key, value, intf_type="ethernet"): + match = None + if intf_type == "svi": + match = re.search(r"line protocol is\s*(\S+)", value, re.M) + else: + match = re.search(r"%s is\s*(\S+)" % key, value, re.M) + + if match: + return match.group(1) + + def parse_macaddress(self, value, intf_type="ethernet"): + match = None + if intf_type == "svi": + match = re.search(r"address is\s*(\S+)", value, re.M) + else: + match = re.search(r"address:\s*(\S+)", value, re.M) + + if match: + return match.group(1) + + def parse_mtu(self, value, intf_type="ethernet"): + match = re.search(r"MTU\s*(\S+)", value, re.M) + if match: + return match.group(1) + + def parse_bandwidth(self, value, intf_type="ethernet"): + match = re.search(r"BW\s*(\S+)", value, re.M) + if match: + return match.group(1) + + def parse_type(self, value, intf_type="ethernet"): + match = None + if intf_type == "svi": + match = re.search(r"Hardware is\s*(\S+)", value, re.M) + else: + match = re.search(r"Hardware:\s*(.+),", value, re.M) + + if match: + return match.group(1) + + def parse_description(self, value, intf_type="ethernet"): + match = re.search(r"Description: (.+)$", value, re.M) + if match: + return match.group(1) + + def parse_mode(self, value, intf_type="ethernet"): + match = re.search(r"Port mode is (\S+)", value, re.M) + if match: + return match.group(1) + + def parse_duplex(self, value, intf_type="ethernet"): + match = re.search(r"(\S+)-duplex", value, re.M) + if match: + return match.group(1) + + def parse_speed(self, value, intf_type="ethernet"): + match = re.search(r"duplex, (.+)$", value, re.M) + if match: + return match.group(1) + + def parse_ipv4_address(self, value, intf_type="ethernet"): + ipv4 = {} + match = re.search(r"Internet Address is (.+)$", value, re.M) + if match: + address = match.group(1) + addr = address.split("/")[0] + ipv4["address"] = address.split("/")[0] + ipv4["masklen"] = address.split("/")[1] + self.facts["all_ipv4_addresses"].append(addr) + return ipv4 + + def populate_neighbors(self, data): + objects = dict() + # if there are no neighbors the show command returns + # ERROR: No neighbour information + if data.startswith("ERROR"): + return dict() + + regex = re.compile(r"(\S+)\s+(\S+)\s+\d+\s+\w+\s+(\S+)") + + for item in data.split("\n")[4:-1]: + match = regex.match(item) + if match: + nbor = dict() + nbor["host"] = nbor["sysname"] = match.group(1) + nbor["port"] = match.group(3) + local_intf = normalize_interface(match.group(2)) + if local_intf not in objects: + objects[local_intf] = [] + objects[local_intf].append(nbor) + + return objects + + def populate_neighbors_cdp(self, data): + facts = dict() + + for item in data.split("----------------------------------------"): + if item == "": + continue + local_intf = self.parse_lldp_intf(item) + if local_intf not in facts: + facts[local_intf] = list() + + fact = dict() + fact["port"] = self.parse_lldp_port(item) + fact["sysname"] = self.parse_lldp_sysname(item) + facts[local_intf].append(fact) + + return facts + + def parse_lldp_intf(self, data): + match = re.search(r"Interface:\s*(\S+)", data, re.M) + if match: + return match.group(1).strip(",") + + def parse_lldp_port(self, data): + match = re.search(r"Port ID \(outgoing port\):\s*(\S+)", data, re.M) + if match: + return match.group(1) + + def parse_lldp_sysname(self, data): + match = re.search(r"Device ID:(.+)$", data, re.M) + if match: + return match.group(1) + + def populate_ipv6_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf["ipv6"] = self.parse_ipv6_address(value) + facts[key] = intf + + def parse_ipv6_address(self, value): + ipv6 = {} + match_addr = re.search(r"IPv6 address:\s*(\S+)", value, re.M) + if match_addr: + addr = match_addr.group(1) + ipv6["address"] = addr + self.facts["all_ipv6_addresses"].append(addr) + match_subnet = re.search(r"IPv6 subnet:\s*(\S+)", value, re.M) + if match_subnet: + ipv6["subnet"] = match_subnet.group(1) + + return ipv6 + + +class Legacy(FactsBase): + # facts from nxos_facts 2.1 + + VERSION_MAP = frozenset( + [ + ("host_name", "_hostname"), + ("kickstart_ver_str", "_os"), + ("chassis_id", "_platform"), + ], + ) + + MODULE_MAP = frozenset( + [ + ("model", "model"), + ("modtype", "type"), + ("ports", "ports"), + ("status", "status"), + ], + ) + + FAN_MAP = frozenset( + [ + ("fanname", "name"), + ("fanmodel", "model"), + ("fanhwver", "hw_ver"), + ("fandir", "direction"), + ("fanstatus", "status"), + ], + ) + + POWERSUP_MAP = frozenset( + [ + ("psmodel", "model"), + ("psnum", "number"), + ("ps_status", "status"), + ("ps_status_3k", "status"), + ("actual_out", "actual_output"), + ("actual_in", "actual_in"), + ("total_capa", "total_capacity"), + ("input_type", "input_type"), + ("watts", "watts"), + ("amps", "amps"), + ], + ) + + def populate(self): + data = None + + data = self.run("show version", output="json") + if data: + if isinstance(data, dict): + self.facts.update(self.transform_dict(data, self.VERSION_MAP)) + else: + self.facts["hostname"] = self.parse_hostname(data) + self.facts["os"] = self.parse_os(data) + self.facts["platform"] = self.parse_platform(data) + + data = self.run("show interface", output="json") + if data: + if isinstance(data, dict): + self.facts["interfaces_list"] = self.parse_structured_interfaces(data) + else: + self.facts["interfaces_list"] = self.parse_interfaces(data) + + data = self.run("show vlan brief", output="json") + if data: + if isinstance(data, dict): + self.facts["vlan_list"] = self.parse_structured_vlans(data) + else: + self.facts["vlan_list"] = self.parse_vlans(data) + + data = self.run("show module", output="json") + if data: + if isinstance(data, dict): + self.facts["module"] = self.parse_structured_module(data) + else: + self.facts["module"] = self.parse_module(data) + + data = self.run("show environment fan", output="json") + if data: + if isinstance(data, dict): + self.facts["fan_info"] = self.parse_structured_fan_info(data) + else: + self.facts["fan_info"] = self.parse_fan_info(data) + + data = self.run("show environment power", output="json") + if data: + if isinstance(data, dict): + self.facts["power_supply_info"] = self.parse_structured_power_supply_info(data) + else: + self.facts["power_supply_info"] = self.parse_power_supply_info(data) + + def parse_structured_interfaces(self, data): + objects = list() + data = data["TABLE_interface"]["ROW_interface"] + if isinstance(data, dict): + objects.append(data["interface"]) + elif isinstance(data, list): + for item in data: + objects.append(item["interface"]) + return objects + + def parse_structured_vlans(self, data): + objects = list() + data = data["TABLE_vlanbriefxbrief"]["ROW_vlanbriefxbrief"] + if isinstance(data, dict): + objects.append(data["vlanshowbr-vlanid-utf"]) + elif isinstance(data, list): + for item in data: + objects.append(item["vlanshowbr-vlanid-utf"]) + return objects + + def parse_structured_module(self, data): + modinfo = data["TABLE_modinfo"] + if isinstance(modinfo, dict): + modinfo = [modinfo] + + objects = [] + for entry in modinfo: + entry = entry["ROW_modinfo"] + if isinstance(entry, dict): + entry = [entry] + entry_objects = list(self.transform_iterable(entry, self.MODULE_MAP)) + objects.extend(entry_objects) + return objects + + def parse_structured_fan_info(self, data): + objects = list() + + for key in ("fandetails", "fandetails_3k"): + if data.get(key): + try: + data = data[key]["TABLE_faninfo"]["ROW_faninfo"] + except KeyError: + # Some virtual images don't actually report faninfo. In this case, move on and + # just return an empty list. + pass + else: + objects = list(self.transform_iterable(data, self.FAN_MAP)) + break + + return objects + + def parse_structured_power_supply_info(self, data): + ps_data = data.get("powersup", {}) + if ps_data.get("TABLE_psinfo_n3k"): + fact = ps_data["TABLE_psinfo_n3k"]["ROW_psinfo_n3k"] + else: + # {TABLE,ROW}_psinfo keys have been renamed to + # {TABLE,ROW}_ps_info in later NX-OS releases + tab_key, row_key = "TABLE_psinfo", "ROW_psinfo" + if tab_key not in ps_data: + tab_key, row_key = "TABLE_ps_info", "ROW_ps_info" + + ps_tab_data = ps_data[tab_key] + + if isinstance(ps_tab_data, list): + fact = [] + for i in ps_tab_data: + fact.append(i[row_key]) + else: + fact = ps_tab_data[row_key] + + objects = list(self.transform_iterable(fact, self.POWERSUP_MAP)) + return objects + + def parse_hostname(self, data): + match = re.search(r"\s+Device name:\s+(\S+)", data, re.M) + if match: + return match.group(1) + + def parse_os(self, data): + match = re.search(r"\s+system:\s+version\s*(\S+)", data, re.M) + if match: + return match.group(1) + else: + match = re.search(r"\s+kickstart:\s+version\s*(\S+)", data, re.M) + if match: + return match.group(1) + + def parse_platform(self, data): + match = re.search(r"Hardware\n\s+cisco\s+(\S+\s+\S+)", data, re.M) + if match: + return match.group(1) + + def parse_interfaces(self, data): + objects = list() + for line in data.split("\n"): + if len(line) == 0: + continue + elif line.startswith("admin") or line[0] == " ": + continue + else: + match = re.match(r"^(\S+)", line) + if match: + intf = match.group(1) + if get_interface_type(intf) != "unknown": + objects.append(intf) + return objects + + def parse_vlans(self, data): + objects = list() + for line in data.splitlines(): + if line == "": + continue + if line[0].isdigit(): + vlan = line.split()[0] + objects.append(vlan) + return objects + + def parse_module(self, data): + objects = list() + for line in data.splitlines(): + if line == "": + break + if line[0].isdigit(): + obj = {} + match_port = re.search(r"\d\s*(\d*)", line, re.M) + if match_port: + obj["ports"] = match_port.group(1) + + match = re.search(r"\d\s*\d*\s*(.+)$", line, re.M) + if match: + l = match.group(1).split(" ") + items = list() + for item in l: + if item == "": + continue + items.append(item.strip()) + + if items: + obj["type"] = items[0] + obj["model"] = items[1] + obj["status"] = items[2] + + objects.append(obj) + return objects + + def parse_fan_info(self, data): + objects = list() + + for l in data.splitlines(): + if "-----------------" in l or "Status" in l: + continue + line = l.split() + if len(line) > 1: + obj = {} + obj["name"] = line[0] + obj["model"] = line[1] + obj["hw_ver"] = line[-2] + obj["status"] = line[-1] + objects.append(obj) + return objects + + def parse_power_supply_info(self, data): + objects = list() + + for l in data.splitlines(): + if l == "": + break + if l[0].isdigit(): + obj = {} + line = l.split() + obj["model"] = line[1] + obj["number"] = line[0] + obj["status"] = line[-1] + + objects.append(obj) + return objects diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/lldp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/lldp_global.py new file mode 100644 index 00000000..6e8aabde --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/lldp_global.py @@ -0,0 +1,107 @@ +# +# -*- 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 nxos lldp_global fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_global.lldp_global import ( + Lldp_globalArgs, +) + + +class Lldp_globalFacts(object): + """The nxos lldp_global fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Lldp_globalArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for lldp_global + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + + if not data: + data = connection.get("show running-config | include lldp") + + objs = {} + objs = self.render_config(self.generated_spec, data) + ansible_facts["ansible_network_resources"].pop("lldp_global", None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {"config": objs}) + facts["lldp_global"] = params["config"] + facts = utils.remove_empties(facts) + 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) + conf = re.split("\n", conf) + for command in conf: + param = re.search(r"(.*)lldp (\w+(-?)\w+)", command) # get the word after 'lldp' + if param: + # get the nested-dict/value for that param + key2 = re.search(r"%s(.*)" % param.group(2), command) + key2 = key2.group(1).strip() + key1 = param.group(2).replace("-", "_") + + if key1 == "portid_subtype": + key1 = "port_id" + config[key1] = key2 + elif key1 == "tlv_select": + key2 = key2.split() + key2[0] = key2[0].replace("-", "_") + if len(key2) == 1: + if "port" in key2[0] or "system" in key2[0]: # nested dicts + key2 = key2[0].split("_") + # config[tlv_select][system][name]=False + config[key1][key2[0]][key2[1]] = False + else: + # config[tlv_select][dcbxp]=False + config[key1][key2[0]] = False + else: + # config[tlv_select][management_address][v6]=False + config[key1][key2[0]][key2[1]] = False + else: + config[key1] = key2 # config[reinit]=4 + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..4cd8b211 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,128 @@ +# +# -*- 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 nxos lldp_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.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_interfaces.lldp_interfaces import ( + Lldp_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_interface_type, +) + + +class Lldp_interfacesFacts(object): + """The nxos lldp_interfaces fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Lldp_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_device_data(self, connection): + return connection.get("show running-config | section ^interface") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for lldp_interfaces + :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_device_data(connection) + + objs = [] + + data = data.split("interface") + resources = [] + + for i in range(len(data)): + intf = data[i].split("\n") + for l in range(1, len(intf)): + if not re.search("lldp", intf[l]): + intf[l] = "" + intf = list(filter(None, intf)) + intf = "".join(i for i in intf) + resources.append(intf) + + for resource in resources: + if resource: # and re.search(r'lldp', resource): + obj = self.render_config(self.generated_spec, resource) + if obj and len(obj.keys()) >= 1: + objs.append(obj) + + ansible_facts["ansible_network_resources"].pop("lldp_interfaces", None) + facts = {} + if objs: + facts["lldp_interfaces"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["lldp_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 + """ + config = deepcopy(spec) + match = re.search(r"^ (\S+)", conf) + if match is None: + return {} + intf = match.group(1) + if get_interface_type(intf) not in ["management", "ethernet"]: + return {} + config["name"] = intf + if "lldp receive" in conf: # for parsed state only + config["receive"] = True + if "no lldp receive" in conf: + config["receive"] = False + + if "lldp transmit" in conf: # for parsed state only + config["transmit"] = True + if "no lldp transmit" in conf: + config["transmit"] = False + if "management-address" in conf: + config["tlv_set"]["management_address"] = re.search( + r"management-address (\S*)", + conf, + ).group(1) + if "vlan" in conf: + config["tlv_set"]["vlan"] = re.search(r"vlan (\S*)", conf).group(1) + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/logging_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/logging_global.py new file mode 100644 index 00000000..189db0e4 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/logging_global.py @@ -0,0 +1,91 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos logging_global fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.logging_global.logging_global import ( + Logging_globalArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.logging_global import ( + Logging_globalTemplate, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( + get_logging_sevmap, +) + + +class Logging_globalFacts(object): + """The nxos logging_global facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Logging_globalArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | include logging") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Logging_global network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + sev_map = get_logging_sevmap() + + if not data: + data = self.get_config(connection) + + # parse native config using the Logging_global template + logging_global_parser = Logging_globalTemplate(lines=data.splitlines(), module=self._module) + objs = logging_global_parser.parse() + + if objs: + for k in ("console", "history", "logfile", "module", "monitor"): + if "severity" in objs.get(k, {}): + objs[k]["severity"] = sev_map[objs[k]["severity"]] + # pre-sort list of dictionaries + pkey = {"hosts": "host", "facilities": "facility"} + for x in ("hosts", "facilities"): + if x in objs: + for item in objs[x]: + if "severity" in item: + item["severity"] = sev_map[item["severity"]] + objs[x] = sorted(objs[x], key=lambda k: k[pkey[x]]) + + ansible_facts["ansible_network_resources"].pop("logging_global", None) + + params = utils.remove_empties( + logging_global_parser.validate_config( + self.argument_spec, + {"config": objs}, + redact=True, + ), + ) + + facts["logging_global"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/ntp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/ntp_global.py new file mode 100644 index 00000000..258b68aa --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/ntp_global.py @@ -0,0 +1,89 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos ntp_global fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ntp_global.ntp_global import ( + Ntp_globalArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ntp_global import ( + Ntp_globalTemplate, +) + + +class Ntp_globalFacts(object): + """The nxos ntp_global facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Ntp_globalArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config ntp") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Ntp_global network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + + if not data: + data = self.get_config(connection) + + # parse native config using the Ntp_global template + ntp_global_parser = Ntp_globalTemplate(lines=data.splitlines(), module=self._module) + objs = ntp_global_parser.parse() + + if "access_group" in objs: + for x in ["peer", "query_only", "serve", "serve_only"]: + if x in objs["access_group"]: + objs["access_group"][x] = sorted( + objs["access_group"][x], + key=lambda k: k["access_list"], + ) + + pkey = { + "authentication_keys": "id", + "peers": "peer", + "servers": "server", + "trusted_keys": "key_id", + } + + for x in pkey.keys(): + if x in objs: + objs[x] = sorted(objs[x], key=lambda k: k[pkey[x]]) + + ansible_facts["ansible_network_resources"].pop("ntp_global", None) + + params = utils.remove_empties( + ntp_global_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), + ) + + facts["ntp_global"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/ospf_interfaces.py new file mode 100644 index 00000000..745f373b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/ospf_interfaces.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# 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 + +""" +The nxos ospf_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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospf_interfaces.ospf_interfaces import ( + Ospf_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospf_interfaces import ( + Ospf_interfacesTemplate, +) + + +class Ospf_interfacesFacts(object): + """The nxos ospf_interfaces facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Ospf_interfacesArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section '^interface'") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Ospf_interfaces network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + + if not data: + data = self.get_config(connection) + + # parse native config using the Ospf_interfaces template + ospf_interfaces_parser = Ospf_interfacesTemplate( + lines=data.splitlines(), + module=self._module, + ) + objs = list(ospf_interfaces_parser.parse().values()) + if objs: + for item in objs: + item["address_family"] = list(item["address_family"].values()) + if "address_family" in item: + for af in item["address_family"]: + if af.get("processes"): + af["processes"] = list(af["processes"].values()) + if af.get("multi_areas"): + af["multi_areas"].sort() + item["address_family"] = sorted(item["address_family"], key=lambda i: i["afi"]) + + objs = sorted( + objs, + key=lambda i: [ + int(k) if k.isdigit() else k for k in i["name"].replace(".", "/").split("/") + ], + ) + + ansible_facts["ansible_network_resources"].pop("ospf_interfaces", None) + + params = utils.remove_empties( + ospf_interfaces_parser.validate_config( + self.argument_spec, + {"config": objs}, + redact=True, + ), + ) + + facts["ospf_interfaces"] = params.get("config", []) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/ospfv2.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/ospfv2.py new file mode 100644 index 00000000..248f3ed4 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/ospfv2.py @@ -0,0 +1,94 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos snmp 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 copy import deepcopy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv2.ospfv2 import ( + Ospfv2Args, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv2 import ( + Ospfv2Template, +) + + +class Ospfv2Facts(object): + """The nxos snmp fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Ospfv2Args.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_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section '^router ospf .*'") + + 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 + """ + if not data: + data = self.get_config(connection) + + ipv4 = {"processes": []} + for section in data.split("router "): + rmmod = Ospfv2Template(lines=section.splitlines()) + entry = rmmod.parse() + + if entry: + global_vals = entry.get("vrfs", {}).pop("vrf_", {}) + for key, value in iteritems(global_vals): + entry[key] = value + + if "vrfs" in entry: + entry["vrfs"] = list(entry["vrfs"].values()) + + for vrf in entry["vrfs"]: + if "areas" in vrf: + vrf["areas"] = list(vrf["areas"].values()) + + if "areas" in entry: + entry["areas"] = list(entry["areas"].values()) + + ipv4["processes"].append(entry) + + ansible_facts["ansible_network_resources"].pop("ospfv2", None) + facts = {} + params = utils.validate_config(self.argument_spec, {"config": ipv4}) + params = utils.remove_empties(params) + + facts["ospfv2"] = params.get("config", []) + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/ospfv3.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/ospfv3.py new file mode 100644 index 00000000..796dacfb --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/ospfv3.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# 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 + +""" +The nxos ospfv3 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 ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv3.ospfv3 import ( + Ospfv3Args, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv3 import ( + Ospfv3Template, +) + + +class Ospfv3Facts(object): + """The nxos ospfv3 facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Ospfv3Args.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section '^router ospfv3'") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Ospfv3 network resource + + :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_config(connection) + + ipv6 = {"processes": []} + for section in data.split("router "): + rmmod = Ospfv3Template(lines=section.splitlines()) + entry = rmmod.parse() + + if entry: + global_vals = entry.get("vrfs", {}).pop("vrf_", {}) + for key, value in iteritems(global_vals): + entry[key] = value + + if "vrfs" in entry: + entry["vrfs"] = list(entry["vrfs"].values()) + + for vrf in entry["vrfs"]: + if "areas" in vrf: + vrf["areas"] = list(vrf["areas"].values()) + + if "areas" in entry: + entry["areas"] = list(entry["areas"].values()) + + if "address_family" in entry: + if "areas" in entry["address_family"]: + entry["address_family"]["areas"] = list( + entry["address_family"]["areas"].values(), + ) + + ipv6["processes"].append(entry) + + ansible_facts["ansible_network_resources"].pop("ospfv3", None) + facts = {} + params = utils.validate_config(self.argument_spec, {"config": ipv6}) + params = utils.remove_empties(params) + + facts["ospfv3"] = params.get("config", []) + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/prefix_lists.py new file mode 100644 index 00000000..7bb18631 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/prefix_lists.py @@ -0,0 +1,79 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos 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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.prefix_lists.prefix_lists import ( + Prefix_listsArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.prefix_lists import ( + Prefix_listsTemplate, +) + + +class Prefix_listsFacts(object): + """The nxos prefix_lists facts class""" + + def __init__(self, module): + self._module = module + self.argument_spec = Prefix_listsArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section 'ip(.*) prefix-list'") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Prefix_lists network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + if not data: + data = self.get_config(connection) + + # parse native config using the Prefix_lists template + prefix_lists_parser = Prefix_listsTemplate(lines=data.splitlines(), module=self._module) + + objs = list(prefix_lists_parser.parse().values()) + if objs: + # pre-sort lists of dictionaries + for item in objs: + item["prefix_lists"] = sorted( + list(item["prefix_lists"].values()), + key=lambda k: k["name"], + ) + for x in item["prefix_lists"]: + if "entries" in x: + x["entries"] = sorted(x["entries"], key=lambda k: k["sequence"]) + objs = sorted(objs, key=lambda k: k["afi"]) + + ansible_facts["ansible_network_resources"].pop("prefix_lists", None) + params = utils.remove_empties( + prefix_lists_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), + ) + facts["prefix_lists"] = params.get("config", []) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/route_maps.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/route_maps.py new file mode 100644 index 00000000..d1bad913 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/route_maps.py @@ -0,0 +1,74 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos route_maps fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.route_maps.route_maps import ( + Route_mapsArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.route_maps import ( + Route_mapsTemplate, +) + + +class Route_mapsFacts(object): + """The nxos route_maps facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Route_mapsArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section '^route-map'") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Route_maps network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + + if not data: + data = self.get_config(connection) + + # parse native config using the Route_maps template + route_maps_parser = Route_mapsTemplate(lines=data.splitlines(), module=self._module) + + objs = list(route_maps_parser.parse().values()) + + for item in objs: + item["entries"] = list(item["entries"].values()) + + ansible_facts["ansible_network_resources"].pop("route_maps", None) + + params = utils.remove_empties( + route_maps_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), + ) + + facts["route_maps"] = params.get("config", []) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/snmp_server.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/snmp_server.py new file mode 100644 index 00000000..c46e1a78 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/snmp_server.py @@ -0,0 +1,85 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The nxos snmp_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 ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.snmp_server.snmp_server import ( + Snmp_serverArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.snmp_server import ( + Snmp_serverTemplate, +) + + +class Snmp_serverFacts(object): + """The nxos snmp_server facts class""" + + def __init__(self, module): + self._module = module + self.argument_spec = Snmp_serverArgs.argument_spec + + def get_config(self, connection): + """Wrapper method for `connection.get()` + This method exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get("show running-config | section '^snmp-server'") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Snmp_server network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + + if not data: + data = self.get_config(connection) + + # parse native config using the Snmp_server template + snmp_server_parser = Snmp_serverTemplate(lines=data.splitlines(), module=self._module) + objs = snmp_server_parser.parse() + + if "communities" in objs: + objs["communities"] = sorted(objs["communities"], key=lambda k: to_text(k["name"])) + + if "users" in objs: + if "auth" in objs["users"]: + objs["users"]["auth"] = sorted( + objs["users"]["auth"], + key=lambda k: to_text(k["user"]), + ) + if "use_acls" in objs["users"]: + objs["users"]["use_acls"] = sorted( + objs["users"]["use_acls"], + key=lambda k: to_text(k["user"]), + ) + + ansible_facts["ansible_network_resources"].pop("snmp_server", None) + + params = utils.remove_empties( + snmp_server_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), + ) + + facts["snmp_server"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/static_routes.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/static_routes.py new file mode 100644 index 00000000..b62d2580 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/static_routes.py @@ -0,0 +1,230 @@ +# +# -*- 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 nxos 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.cisco.nxos.plugins.module_utils.network.nxos.argspec.static_routes.static_routes import ( + Static_routesArgs, +) + + +class Static_routesFacts(object): + """The nxos 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 get_device_data(self, connection, data): + vrf_data = [] + non_vrf_data = [] + if not data: + non_vrf_data = connection.get("show running-config | include '^ip(v6)* route'") + vrf_data = connection.get("show running-config | section '^vrf context'") + if non_vrf_data: + non_vrf_data = non_vrf_data.split("\n") + else: + non_vrf_data = [] + vrf_data = vrf_data.split("\nvrf context") + # as we split based on 'vrf context', it is stripped from the data except the first element + else: + # used for parsed state where data is from the 'running-config' key + data = data.split("\n") + i = 0 + while i <= (len(data) - 1): + if "vrf context " in data[i]: + vrf_conf = data[i] + j = i + 1 + while j < len(data) and "vrf context " not in data[j]: + vrf_conf += "\n" + data[j] + j += 1 + i = j + vrf_data.append(vrf_conf) + else: + non_vrf_data.append(data[i]) + i += 1 + + new_vrf_data = [] + for v in vrf_data: + if re.search(r"\n\s*ip(v6)? route", v): + new_vrf_data.append(v) + # dont consider vrf if it does not have routes + for i in range(len(new_vrf_data)): + if not re.search("^vrf context", new_vrf_data[i]): + new_vrf_data[i] = "vrf context" + new_vrf_data[i] + + resources = non_vrf_data + new_vrf_data + return resources + + 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 = [] + resources = self.get_device_data(connection, data) + objs = self.render_config(self.generated_spec, resources) + ansible_facts["ansible_network_resources"].pop("static_routes", None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {"config": objs}) + params = utils.remove_empties(params) + for c in params["config"]: + if c == {"vrf": "default"}: + params["config"].remove(c) + facts["static_routes"] = params["config"] + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def get_inner_dict(self, conf, inner_dict): + """ + This method parses the command to create the innermost dictionary of the config + """ + conf = re.sub(r"\s*ip(v6)? route", "", conf) + # strip 'ip route' + inner_dict["dest"] = re.match(r"^\s*(\S+\/\d+) .*", conf).group(1) + + # ethernet1/2/23 + iface = re.match(r".* (Ethernet|loopback|mgmt|port\-channel)(\S*) .*", conf) + i = ["Ethernet", "loopback", "mgmt", "port-channel"] + if iface and iface.group(1) in i: + inner_dict["interface"] = (iface.group(1)) + (iface.group(2)) + conf = re.sub(inner_dict["interface"], "", conf) + + if "." in inner_dict["dest"]: + conf = re.sub(inner_dict["dest"], "", conf) + inner_dict["afi"] = "ipv4" + ipv4 = re.match(r".* (\d+\.\d+\.\d+\.\d+\/?\d*).*", conf) # gets next hop ip + if ipv4: + inner_dict["forward_router_address"] = ipv4.group(1) + conf = re.sub(inner_dict["forward_router_address"], "", conf) + else: + inner_dict["afi"] = "ipv6" + conf = re.sub(inner_dict["dest"], "", conf) + ipv6 = re.match(r".* (\S*:\S*:\S*\/?\d*).*", conf) + if ipv6: + inner_dict["forward_router_address"] = ipv6.group(1) + conf = re.sub(inner_dict["forward_router_address"], "", conf) + + nullif = re.search(r"null0", conf, re.IGNORECASE) + if nullif: + inner_dict["interface"] = "Null0" + inner_dict["forward_router_address"] = None + return inner_dict # dest IP not needed for null if + + keywords = ["vrf", "name", "tag", "track"] + for key in keywords: + pattern = re.match(r".* (?:%s) (\S+).*" % key, conf) + if pattern: + if key == "vrf": + key = "dest_vrf" + elif key == "name": + key = "route_name" + inner_dict[key] = pattern.group(1).strip() + conf = re.sub(key + " " + inner_dict[key], "", conf) + + pref = re.match(r"(?:.*) (\d+)$", conf) + if pref: + # if something is left at the end without any key, it is the pref + inner_dict["admin_distance"] = pref.group(1) + return inner_dict + + def get_command(self, conf, afi_list, dest_list, af): + inner_dict = {} + inner_dict = self.get_inner_dict(conf, inner_dict) + if inner_dict["afi"] not in afi_list: + af.append({"afi": inner_dict["afi"], "routes": []}) + afi_list.append(inner_dict["afi"]) + + next_hop = {} + params = [ + "forward_router_address", + "interface", + "admin_distance", + "route_name", + "tag", + "track", + "dest_vrf", + ] + for p in params: + if p in inner_dict.keys(): + next_hop.update({p: inner_dict[p]}) + + if inner_dict["dest"] not in dest_list: + dest_list.append(inner_dict["dest"]) + af[-1]["routes"].append({"dest": inner_dict["dest"], "next_hops": []}) + # if 'dest' is new, create new list under 'routes' + af[-1]["routes"][-1]["next_hops"].append(next_hop) + else: + af[-1]["routes"][-1]["next_hops"].append(next_hop) + # just append if dest already exists + return af + + def render_config(self, spec, con): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + # config=deepcopy(spec) + config = [] + global_afi_list = [] + global_af = [] + global_dest_list = [] + if con: + for conf in con: + if conf.startswith("vrf context"): + svrf = re.match(r"vrf context (\S+)\n", conf).group(1) + afi_list = [] + af = [] + dest_list = [] + config_dict = {"vrf": svrf, "address_families": []} + conf = conf.split("\n") + # considering from the second line as first line is 'vrf context..' + conf = conf[1:] + for c in conf: + if ("ip route" in c or "ipv6 route" in c) and "bfd" not in c: + self.get_command(c, afi_list, dest_list, af) + config_dict["address_families"] = af + config.append(config_dict) + else: + if ("ip route" in conf or "ipv6 route" in conf) and "bfd" not in conf: + self.get_command(conf, global_afi_list, global_dest_list, global_af) + if global_af: + config.append(utils.remove_empties({"address_families": global_af})) + return config diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/telemetry.py new file mode 100644 index 00000000..bdb28031 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/telemetry.py @@ -0,0 +1,185 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The nxos telemetry 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.cisco.nxos.plugins.module_utils.network.nxos.argspec.telemetry.telemetry import ( + TelemetryArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.cmdref.telemetry.telemetry import ( + TMS_DESTGROUP, + TMS_GLOBAL, + TMS_SENSORGROUP, + TMS_SUBSCRIPTION, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import NxosCmdRef +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.telemetry.telemetry import ( + cr_key_lookup, + get_instance_data, + normalize_data, +) + + +class TelemetryFacts(object): + """The nxos telemetry fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = TelemetryArgs.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 telemetry + :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 + + cmd_ref = {} + cmd_ref["TMS_GLOBAL"] = {} + cmd_ref["TMS_DESTGROUP"] = {} + cmd_ref["TMS_SENSORGROUP"] = {} + cmd_ref["TMS_SUBSCRIPTION"] = {} + + # For fact gathering, module state should be 'present' when using + # NxosCmdRef to query state + if self._module.params.get("state"): + saved_module_state = self._module.params["state"] + self._module.params["state"] = "present" + + # Get Telemetry Global Data + cmd_ref["TMS_GLOBAL"]["ref"] = [] + cmd_ref["TMS_GLOBAL"]["ref"].append(NxosCmdRef(self._module, TMS_GLOBAL)) + ref = cmd_ref["TMS_GLOBAL"]["ref"][0] + ref.set_context() + ref.get_existing() + device_cache = ref.cache_existing + + if device_cache is None: + device_cache_lines = [] + else: + device_cache_lines = device_cache.split("\n") + + # Get Telemetry Destination Group Data + cmd_ref["TMS_DESTGROUP"]["ref"] = [] + for line in device_cache_lines: + if re.search(r"destination-group", line): + resource_key = line.strip() + cmd_ref["TMS_DESTGROUP"]["ref"].append(NxosCmdRef(self._module, TMS_DESTGROUP)) + ref = cmd_ref["TMS_DESTGROUP"]["ref"][-1] + ref.set_context([resource_key]) + ref.get_existing(device_cache) + normalize_data(ref) + + # Get Telemetry Sensorgroup Group Data + cmd_ref["TMS_SENSORGROUP"]["ref"] = [] + for line in device_cache_lines: + if re.search(r"sensor-group", line): + resource_key = line.strip() + cmd_ref["TMS_SENSORGROUP"]["ref"].append(NxosCmdRef(self._module, TMS_SENSORGROUP)) + ref = cmd_ref["TMS_SENSORGROUP"]["ref"][-1] + ref.set_context([resource_key]) + ref.get_existing(device_cache) + + # Get Telemetry Subscription Data + cmd_ref["TMS_SUBSCRIPTION"]["ref"] = [] + for line in device_cache_lines: + if re.search(r"subscription", line): + resource_key = line.strip() + cmd_ref["TMS_SUBSCRIPTION"]["ref"].append( + NxosCmdRef(self._module, TMS_SUBSCRIPTION), + ) + ref = cmd_ref["TMS_SUBSCRIPTION"]["ref"][-1] + ref.set_context([resource_key]) + ref.get_existing(device_cache) + + objs = [] + objs = self.render_config(self.generated_spec, cmd_ref) + facts = {"telemetry": {}} + if objs: + # params = utils.validate_config(self.argument_spec, {'config': objs}) + facts["telemetry"] = objs + + ansible_facts["ansible_network_resources"].update(facts) + if self._module.params.get("state"): + self._module.params["state"] = saved_module_state + return ansible_facts + + def render_config(self, spec, cmd_ref): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + config["destination_groups"] = [] + config["sensor_groups"] = [] + config["subscriptions"] = [] + managed_objects = [ + "TMS_GLOBAL", + "TMS_DESTGROUP", + "TMS_SENSORGROUP", + "TMS_SUBSCRIPTION", + ] + + # Walk the argspec and cmd_ref objects and build out config dict. + for key in config.keys(): + for mo in managed_objects: + for cr in cmd_ref[mo]["ref"]: + cr_keys = cr_key_lookup(key, mo) + for cr_key in cr_keys: + if cr._ref.get(cr_key) and cr._ref[cr_key].get("existing"): + if isinstance(config[key], dict): + for k in config[key].keys(): + for existing_key in cr._ref[cr_key]["existing"].keys(): + config[key][k] = cr._ref[cr_key]["existing"][existing_key][ + k + ] + continue + if isinstance(config[key], list): + for existing_key in cr._ref[cr_key]["existing"].keys(): + data = get_instance_data(key, cr_key, cr, existing_key) + config[key].append(data) + continue + for existing_key in cr._ref[cr_key]["existing"].keys(): + config[key] = cr._ref[cr_key]["existing"][existing_key] + elif cr._ref.get(cr_key): + data = get_instance_data(key, cr_key, cr, None) + if isinstance(config[key], list) and data not in config[key]: + config[key].append(data) + + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/vlans.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/vlans.py new file mode 100644 index 00000000..32968d2d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/vlans.py @@ -0,0 +1,197 @@ +# +# -*- 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)#!/usr/bin/python + +""" +The nxos 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 ast +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + parse_conf_arg, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vlans.vlans import ( + VlansArgs, +) + + +class VlansFacts(object): + """The nxos 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 get_device_data(self, connection, show_cmd): + """Wrapper method for `connection.get()` + This exists solely to allow the unit test framework to mock device connection calls. + """ + return connection.get(show_cmd) + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for vlans + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + # **TBD** + # N7K EOL/legacy image 6.2 does not support show vlan | json output. + # If support is still required for this image then: + # - Wrapp the json calls below in a try/except + # - When excepted, use a helper method to parse the run_cfg_output, + # using the run_cfg_output data to generate compatible json data that + # can be read by normalize_table_data. + if not data: + # Use structured for most of the vlan parameter states. + # This data is consistent across the supported nxos platforms. + try: + # Not all devices support | json-pretty but is a workaround for + # libssh issue https://github.com/ansible/pylibssh/issues/208 + structured = self.get_device_data(connection, "show vlan | json-pretty") + except Exception: + # When json-pretty is not supported, we fall back to | json + structured = self.get_device_data(connection, "show vlan | json") + + # Raw cli config is needed for mapped_vni, which is not included in structured. + run_cfg_output = self.get_device_data(connection, "show running-config | section ^vlan") + else: + running_config = data.split("\n\n") + structured, run_cfg_output = running_config[0], running_config[1] + + # Create a single dictionary from all data sources + data = self.normalize_table_data(structured, run_cfg_output) + + for vlan in data: + obj = self.render_config(self.generated_spec, vlan) + if obj: + objs.append(obj) + + ansible_facts["ansible_network_resources"].pop("vlans", None) + facts = {} + if objs: + facts["vlans"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["vlans"].append(utils.remove_empties(cfg)) + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def render_config(self, spec, vlan): + """ + Render config as dictionary structure and delete keys + from spec for null values + :param spec: The facts tree, generated from the argspec + :param vlan: structured data vlan settings (dict) and raw cfg from device + :rtype: dictionary + :returns: The generated config + Sample inputs: test/units/modules/network/nxos/fixtures/nxos_vlans/show_vlan + """ + obj = deepcopy(spec) + + obj["vlan_id"] = vlan["vlan_id"] + + # name: 'VLAN000x' (default name) or custom name + name = vlan["vlanshowbr-vlanname"] + if name and re.match("VLAN%04d" % int(vlan["vlan_id"]), name): + name = None + obj["name"] = name + + # mode: 'ce-vlan' or 'fabricpath-vlan' + obj["mode"] = vlan["vlanshowinfo-vlanmode"].replace("-vlan", "") + + # enabled: shutdown, noshutdown + obj["enabled"] = True if "noshutdown" in vlan["vlanshowbr-shutstate"] else False + + # state: active, suspend + obj["state"] = vlan["vlanshowbr-vlanstate"] + + # non-structured data + obj["mapped_vni"] = parse_conf_arg(vlan["run_cfg"], "vn-segment") + + return utils.remove_empties(obj) + + def normalize_table_data(self, structured, run_cfg_output): + """Normalize structured output and raw running-config output into + a single dict to simplify render_config usage. + This is needed because: + - The NXOS devices report most of the vlan settings within two + structured data keys: 'vlanbrief' and 'mtuinfo', but the output is + incomplete and therefore raw running-config data is also needed. + - running-config by itself is insufficient because of major differences + in the cli config syntax across platforms. + - Thus a helper method combines settings from the separate top-level keys, + and adds a 'run_cfg' key containing raw cli from the device. + """ + # device output may be string, convert to list + structured = ast.literal_eval(str(structured)) + + vlanbrief = [] + mtuinfo = [] + if "TABLE_vlanbrief" in structured: + # SAMPLE: {"TABLE_vlanbriefid": {"ROW_vlanbriefid": { + # "vlanshowbr-vlanid": "4", "vlanshowbr-vlanid-utf": "4", + # "vlanshowbr-vlanname": "VLAN0004", "vlanshowbr-vlanstate": "active", + # "vlanshowbr-shutstate": "noshutdown"}}, + vlanbrief = structured["TABLE_vlanbrief"]["ROW_vlanbrief"] + + # SAMPLE: "TABLE_mtuinfoid": {"ROW_mtuinfoid": { + # "vlanshowinfo-vlanid": "4", "vlanshowinfo-media-type": "enet", + # "vlanshowinfo-vlanmode": "ce-vlan"}} + mtuinfo = structured["TABLE_mtuinfo"]["ROW_mtuinfo"] + + if type(vlanbrief) is not list: + # vlanbrief is not a list when only one vlan is found. + vlanbrief = [vlanbrief] + mtuinfo = [mtuinfo] + + # split out any per-vlan cli config + run_cfg_list = re.split(r"[\n^]vlan ", run_cfg_output) + + # Create a list of vlan dicts where each dict contains vlanbrief, + # mtuinfo, and non-structured running-config data for one vlan. + vlans = [] + for index, v in enumerate(vlanbrief): + v["vlan_id"] = v.get("vlanshowbr-vlanid-utf") + vlan = {} + vlan.update(v) + vlan.update(mtuinfo[index]) + + vlan["run_cfg"] = "" + for item in run_cfg_list: + # Sample match lines + # 202\n name Production-Segment-100101\n vn-segment 100101 + # 5\n state suspend\n shutdown\n name test-changeme\n vn-segment 942 + pattern = r"^{0}\s+\S.*vn-segment".format(v["vlan_id"]) + if re.search(pattern, item, flags=re.DOTALL): + vlan["run_cfg"] = item + break + + vlans.append(vlan) + return vlans diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/nxos.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/nxos.py new file mode 100644 index 00000000..f0e3843e --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/nxos.py @@ -0,0 +1,1031 @@ +# +# 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. +# +# Copyright: (c) 2017, 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 copy import deepcopy + +from ansible.module_utils._text import to_text +from ansible.module_utils.common._collections_compat import Mapping +from ansible.module_utils.connection import Connection, ConnectionError +from ansible.module_utils.six import PY2, PY3 +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, + NetworkConfig, + dumps, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + ComplexList, + to_list, +) + + +try: + import yaml + + HAS_YAML = True +except ImportError: + HAS_YAML = False + + +_DEVICE_CONNECTION = None + + +def get_connection(module): + global _DEVICE_CONNECTION + if not _DEVICE_CONNECTION: + connection_proxy = Connection(module._socket_path) + cap = json.loads(connection_proxy.get_capabilities()) + if cap["network_api"] == "cliconf": + conn = Cli(module) + elif cap["network_api"] == "nxapi": + conn = HttpApi(module) + _DEVICE_CONNECTION = conn + return _DEVICE_CONNECTION + + +class Cli: + def __init__(self, module): + self._module = module + self._device_configs = {} + self._connection = None + + def _get_connection(self): + if self._connection: + return self._connection + self._connection = Connection(self._module._socket_path) + + return self._connection + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache""" + flags = [] if flags is None else flags + + cmd = "show running-config " + cmd += " ".join(flags) + cmd = cmd.strip() + + try: + return self._device_configs[cmd] + except KeyError: + connection = self._get_connection() + try: + out = connection.get_config(flags=flags) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + cfg = to_text(out, errors="surrogate_then_replace").strip() + "\n" + self._device_configs[cmd] = cfg + return cfg + + def run_commands(self, commands, check_rc=True): + """Run list of commands on remote device and return results""" + connection = self._get_connection() + + try: + out = connection.run_commands(commands, check_rc) + if check_rc == "retry_json": + capabilities = self.get_capabilities() + network_api = capabilities.get("network_api") + + if network_api == "cliconf" and out: + for index, resp in enumerate(out): + if ( + "Invalid command at" in resp or "Ambiguous command at" in resp + ) and "json" in resp: + if commands[index]["output"] == "json": + commands[index]["output"] = "text" + out = connection.run_commands(commands, check_rc) + return out + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc)) + + def load_config(self, config, return_error=False, opts=None, replace=None): + """Sends configuration commands to the remote device""" + if opts is None: + opts = {} + + connection = self._get_connection() + responses = [] + try: + resp = connection.edit_config(config, replace=replace) + if isinstance(resp, Mapping): + resp = resp["response"] + except ConnectionError as e: + code = getattr(e, "code", 1) + message = getattr(e, "err", e) + err = to_text(message, errors="surrogate_then_replace") + if opts.get("ignore_timeout") and code: + responses.append(err) + return responses + elif code and "no graceful-restart" in err: + if "ISSU/HA will be affected if Graceful Restart is disabled" in err: + msg = [""] + responses.extend(msg) + return responses + else: + self._module.fail_json(msg=err) + elif code: + self._module.fail_json(msg=err) + + responses.extend(resp) + return responses + + def get_diff( + self, + candidate=None, + running=None, + diff_match="line", + diff_ignore_lines=None, + path=None, + diff_replace="line", + ): + conn = self._get_connection() + try: + response = conn.get_diff( + candidate=candidate, + running=running, + diff_match=diff_match, + diff_ignore_lines=diff_ignore_lines, + path=path, + diff_replace=diff_replace, + ) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + return response + + def get_capabilities(self): + """Returns platform info of the remove device""" + if hasattr(self._module, "_capabilities"): + return self._module._capabilities + + connection = self._get_connection() + try: + capabilities = connection.get_capabilities() + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + self._module._capabilities = json.loads(capabilities) + return self._module._capabilities + + def read_module_context(self, module_key): + connection = self._get_connection() + try: + module_context = connection.read_module_context(module_key) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + return module_context + + def save_module_context(self, module_key, module_context): + connection = self._get_connection() + try: + connection.save_module_context(module_key, module_context) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + return None + + +class HttpApi: + def __init__(self, module): + self._module = module + self._device_configs = {} + self._module_context = {} + self._connection_obj = None + + @property + def _connection(self): + if not self._connection_obj: + self._connection_obj = Connection(self._module._socket_path) + + return self._connection_obj + + def run_commands(self, commands, check_rc=True): + """Runs list of commands on remote device and returns results""" + try: + out = self._connection.send_request(commands) + except ConnectionError as exc: + if check_rc is True: + raise + out = to_text(exc) + + out = to_list(out) + if not out[0]: + return out + + for index, response in enumerate(out): + if response[0] == "{": + out[index] = json.loads(response) + + return out + + def get_config(self, flags=None): + """Retrieves the current config from the device or cache""" + flags = [] if flags is None else flags + + cmd = "show running-config " + cmd += " ".join(flags) + cmd = cmd.strip() + + try: + return self._device_configs[cmd] + except KeyError: + try: + out = self._connection.send_request(cmd) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + cfg = to_text(out).strip() + self._device_configs[cmd] = cfg + return cfg + + def get_diff( + self, + candidate=None, + running=None, + diff_match="line", + diff_ignore_lines=None, + path=None, + diff_replace="line", + ): + diff = {} + + # prepare candidate configuration + candidate_obj = NetworkConfig(indent=2) + candidate_obj.load(candidate) + + if running and diff_match != "none" and diff_replace != "config": + # running configuration + running_obj = NetworkConfig(indent=2, contents=running, ignore_lines=diff_ignore_lines) + configdiffobjs = candidate_obj.difference( + running_obj, + path=path, + match=diff_match, + replace=diff_replace, + ) + + else: + configdiffobjs = candidate_obj.items + + diff["config_diff"] = dumps(configdiffobjs, "commands") if configdiffobjs else "" + return diff + + def load_config(self, commands, return_error=False, opts=None, replace=None): + """Sends the ordered set of commands to the device""" + if opts is None: + opts = {} + + responses = [] + try: + resp = self.edit_config(commands, replace=replace) + except ConnectionError as exc: + code = getattr(exc, "code", 1) + message = getattr(exc, "err", exc) + err = to_text(message, errors="surrogate_then_replace") + if opts.get("ignore_timeout") and code: + responses.append(code) + return responses + elif opts.get("catch_clierror") and "400" in code: + return [code, err] + elif code and "no graceful-restart" in err: + if "ISSU/HA will be affected if Graceful Restart is disabled" in err: + msg = [""] + responses.extend(msg) + return responses + else: + self._module.fail_json(msg=err) + elif code: + self._module.fail_json(msg=err) + + responses.extend(resp) + return responses + + def edit_config(self, candidate=None, commit=True, replace=None, comment=None): + resp = list() + + self.check_edit_config_capability(candidate, commit, replace, comment) + + if replace: + candidate = "config replace {0}".format(replace) + + responses = self._connection.send_request(candidate, output="config") + for response in to_list(responses): + if response != "{}": + resp.append(response) + if not resp: + resp = [""] + + return resp + + def get_capabilities(self): + """Returns platform info of the remove device""" + try: + capabilities = self._connection.get_capabilities() + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + return json.loads(capabilities) + + def check_edit_config_capability(self, candidate=None, commit=True, replace=None, comment=None): + operations = self._connection.get_device_operations() + + if not candidate and not replace: + raise ValueError("must provide a candidate or replace to load configuration") + + if commit not in (True, False): + raise ValueError("'commit' must be a bool, got %s" % commit) + + if replace and not operations.get("supports_replace"): + raise ValueError("configuration replace is not supported") + + if comment and not operations.get("supports_commit_comment", False): + raise ValueError("commit comment is not supported") + + def read_module_context(self, module_key): + try: + module_context = self._connection.read_module_context(module_key) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + return module_context + + def save_module_context(self, module_key, module_context): + try: + self._connection.save_module_context(module_key, module_context) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + return None + + +class NxosCmdRef: + """NXOS Command Reference utilities. + The NxosCmdRef class takes a yaml-formatted string of nxos module commands + and converts it into dict-formatted database of getters/setters/defaults + and associated common and platform-specific values. The utility methods + add additional data such as existing states, playbook states, and proposed cli. + The utilities also abstract away platform differences such as different + defaults and different command syntax. + + Callers must provide a yaml formatted string that defines each command and + its properties; e.g. BFD global: + --- + _template: # _template holds common settings for all commands + # Enable feature bfd if disabled + feature: bfd + # Common getter syntax for BFD commands + get_command: show run bfd all | incl '^(no )*bfd' + + interval: + kind: dict + getval: bfd interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+) + setval: bfd interval {tx} min_rx {min_rx} multiplier {multiplier} + default: + tx: 50 + min_rx: 50 + multiplier: 3 + N3K: + # Platform overrides + default: + tx: 250 + min_rx: 250 + multiplier: 3 + """ + + def __init__(self, module, cmd_ref_str, ref_only=False): + """Initialize cmd_ref from yaml data.""" + + self._module = module + self._check_imports() + self._yaml_load(cmd_ref_str) + self.cache_existing = None + self.present_states = ["present", "merged", "replaced"] + self.absent_states = ["absent", "deleted"] + ref = self._ref + + # Create a list of supported commands based on ref keys + ref["commands"] = sorted([k for k in ref if not k.startswith("_")]) + ref["_proposed"] = [] + ref["_context"] = [] + ref["_resource_key"] = None + + if not ref_only: + ref["_state"] = module.params.get("state", "present") + self.feature_enable() + self.get_platform_defaults() + self.normalize_defaults() + + def __getitem__(self, key=None): + if key is None: + return self._ref + return self._ref[key] + + def _check_imports(self): + module = self._module + msg = nxosCmdRef_import_check() + if msg: + module.fail_json(msg=msg) + + def _yaml_load(self, cmd_ref_str): + if PY2: + self._ref = yaml.load(cmd_ref_str) + elif PY3: + self._ref = yaml.load(cmd_ref_str, Loader=yaml.FullLoader) + + def feature_enable(self): + """Add 'feature <foo>' to _proposed if ref includes a 'feature' key.""" + ref = self._ref + feature = ref["_template"].get("feature") + if feature: + show_cmd = "show run | incl 'feature {0}'".format(feature) + output = self.execute_show_command(show_cmd, "text") + if not output or "CLI command error" in output: + msg = "** 'feature {0}' is not enabled. Module will auto-enable feature {0} ** ".format( + feature, + ) + self._module.warn(msg) + ref["_proposed"].append("feature {0}".format(feature)) + ref["_cli_is_feature_disabled"] = ref["_proposed"] + + def get_platform_shortname(self): + """Query device for platform type, normalize to a shortname/nickname. + Returns platform shortname (e.g. 'N3K-3058P' returns 'N3K') or None. + """ + # TBD: add this method logic to get_capabilities() after those methods + # are made consistent across transports + platform_info = self.execute_show_command("show inventory", "json") + if not platform_info or not isinstance(platform_info, dict): + return None + inventory_table = platform_info["TABLE_inv"]["ROW_inv"] + for info in inventory_table: + if "Chassis" in info["name"]: + network_os_platform = info["productid"] + break + else: + return None + + # Supported Platforms: N3K,N5K,N6K,N7K,N9K,N3K-F,N9K-F + m = re.match("(?P<short>N[35679][K57])-(?P<N35>C35)*", network_os_platform) + if not m: + return None + shortname = m.group("short") + + # Normalize + if m.groupdict().get("N35"): + shortname = "N35" + elif re.match("N77", shortname): + shortname = "N7K" + elif re.match(r"N3K|N9K", shortname): + for info in inventory_table: + if "-R" in info["productid"]: + # Fretta Platform + shortname += "-F" + break + return shortname + + def get_platform_defaults(self): + """Update ref with platform specific defaults""" + plat = self.get_platform_shortname() + if not plat: + return + + ref = self._ref + ref["_platform_shortname"] = plat + # Remove excluded commands (no platform support for command) + for k in ref["commands"]: + if plat in ref[k].get("_exclude", ""): + ref["commands"].remove(k) + + # Update platform-specific settings for each item in ref + plat_spec_cmds = [k for k in ref["commands"] if plat in ref[k]] + for k in plat_spec_cmds: + for plat_key in ref[k][plat]: + ref[k][plat_key] = ref[k][plat][plat_key] + + def normalize_defaults(self): + """Update ref defaults with normalized data""" + ref = self._ref + for k in ref["commands"]: + if "default" in ref[k] and ref[k]["default"]: + kind = ref[k]["kind"] + if "int" == kind: + ref[k]["default"] = int(ref[k]["default"]) + elif "list" == kind: + ref[k]["default"] = [str(i) for i in ref[k]["default"]] + elif "dict" == kind: + for key, v in ref[k]["default"].items(): + if v: + v = str(v) + ref[k]["default"][key] = v + + def execute_show_command(self, command, format): + """Generic show command helper. + Warning: 'CLI command error' exceptions are caught, must be handled by caller. + Return device output as a newline-separated string or None. + """ + cmds = [{"command": command, "output": format}] + output = None + try: + output = run_commands(self._module, cmds) + if output: + output = output[0] + except ConnectionError as exc: + if "CLI command error" in repr(exc): + # CLI may be feature disabled + output = repr(exc) + else: + raise + return output + + def pattern_match_existing(self, output, k): + """Pattern matching helper for `get_existing`. + `k` is the command name string. Use the pattern from cmd_ref to + find a matching string in the output. + Return regex match object or None. + """ + ref = self._ref + pattern = re.compile(ref[k]["getval"]) + multiple = "multiple" in ref[k].keys() + match_lines = [re.search(pattern, line) for line in output] + if "dict" == ref[k]["kind"]: + match = [m for m in match_lines if m] + if not match: + return None + if len(match) > 1 and not multiple: + raise ValueError("get_existing: multiple matches found for property {0}".format(k)) + else: + match = [m.groups() for m in match_lines if m] + if not match: + return None + if len(match) > 1 and not multiple: + raise ValueError("get_existing: multiple matches found for property {0}".format(k)) + for item in match: + index = match.index(item) + match[index] = list(item) # tuple to list + + # Handle config strings that nvgen with the 'no' prefix. + # Example match behavior: + # When pattern is: '(no )*foo *(\S+)*$' AND + # When output is: 'no foo' -> match: ['no ', None] + # When output is: 'foo 50' -> match: [None, '50'] + if None is match[index][0]: + match[index].pop(0) + elif "no" in match[index][0]: + match[index].pop(0) + if not match: + return None + + return match + + def set_context(self, context=None): + """Update ref with command context.""" + if context is None: + context = [] + ref = self._ref + # Process any additional context that this propoerty might require. + # 1) Global context from NxosCmdRef _template. + # 2) Context passed in using context arg. + ref["_context"] = ref["_template"].get("context", []) + for cmd in context: + ref["_context"].append(cmd) + # Last key in context is the resource key + ref["_resource_key"] = context[-1] if context else ref["_resource_key"] + + def get_existing(self, cache_output=None): + """Update ref with existing command states from the device. + Store these states in each command's 'existing' key. + """ + ref = self._ref + if ref.get("_cli_is_feature_disabled"): + # Add context to proposed if state is present + if ref["_state"] in self.present_states: + [ref["_proposed"].append(ctx) for ctx in ref["_context"]] + return + + show_cmd = ref["_template"]["get_command"] + if cache_output: + output = cache_output + else: + output = self.execute_show_command(show_cmd, "text") or [] + self.cache_existing = output + + # Add additional command context if needed. + if ref["_context"]: + output = CustomNetworkConfig(indent=2, contents=output) + output = output.get_section(ref["_context"]) + + if not output: + # Add context to proposed if state is present + if ref["_state"] in self.present_states: + [ref["_proposed"].append(ctx) for ctx in ref["_context"]] + return + + # We need to remove the last item in context for state absent case. + if ref["_state"] in self.absent_states and ref["_context"]: + if ref["_resource_key"] and ref["_resource_key"] == ref["_context"][-1]: + if ref["_context"][-1] in output: + ref["_context"][-1] = "no " + ref["_context"][-1] + else: + del ref["_context"][-1] + return + + # Walk each cmd in ref, use cmd pattern to discover existing cmds + output = output.split("\n") + for k in ref["commands"]: + match = self.pattern_match_existing(output, k) + if not match: + continue + ref[k]["existing"] = {} + for item in match: + index = match.index(item) + kind = ref[k]["kind"] + if "int" == kind: + ref[k]["existing"][index] = int(item[0]) + elif "list" == kind: + ref[k]["existing"][index] = [str(i) for i in item[0]] + elif "dict" == kind: + # The getval pattern should contain regex named group keys that + # match up with the setval named placeholder keys; e.g. + # getval: my-cmd (?P<foo>\d+) bar (?P<baz>\d+) + # setval: my-cmd {foo} bar {baz} + ref[k]["existing"][index] = {} + for key in item.groupdict().keys(): + ref[k]["existing"][index][key] = str(item.group(key)) + elif "str" == kind: + ref[k]["existing"][index] = item[0] + else: + raise ValueError( + "get_existing: unknown 'kind' value specified for key '{0}'".format(k), + ) + + def get_playvals(self): + """Update ref with values from the playbook. + Store these values in each command's 'playval' key. + """ + ref = self._ref + module = self._module + params = {} + if module.params.get("config"): + # Resource module builder packs playvals under 'config' key + param_data = module.params.get("config") + params["global"] = param_data + for key in param_data.keys(): + if isinstance(param_data[key], list): + params[key] = param_data[key] + else: + params["global"] = module.params + for k in ref.keys(): + for level in params.keys(): + if isinstance(params[level], dict): + params[level] = [params[level]] + for item in params[level]: + if k in item and item[k] is not None: + if not ref[k].get("playval"): + ref[k]["playval"] = {} + playval = item[k] + index = params[level].index(item) + # Normalize each value + if "int" == ref[k]["kind"]: + playval = int(playval) + elif "list" == ref[k]["kind"]: + playval = [str(i) for i in playval] + elif "dict" == ref[k]["kind"]: + for key, v in playval.items(): + playval[key] = str(v) + ref[k]["playval"][index] = playval + + def build_cmd_set(self, playval, existing, k): + """Helper function to create list of commands to configure device + Return a list of commands + """ + ref = self._ref + proposed = ref["_proposed"] + cmd = None + kind = ref[k]["kind"] + if "int" == kind: + cmd = ref[k]["setval"].format(playval) + elif "list" == kind: + cmd = ref[k]["setval"].format(*(playval)) + elif "dict" == kind: + # The setval pattern should contain placeholder keys that + # match up with the getval regex named group keys; e.g. + # getval: my-cmd (?P<foo>\d+) bar (?P<baz>\d+) + # setval: my-cmd {foo} bar {baz} + if ref[k]["setval"].startswith("path"): + tmplt = "path {name}" + if "depth" in playval: + tmplt += " depth {depth}" + if "query_condition" in playval: + tmplt += " query-condition {query_condition}" + if "filter_condition" in playval: + tmplt += " filter-condition {filter_condition}" + cmd = tmplt.format(**playval) + else: + cmd = ref[k]["setval"].format(**playval) + elif "str" == kind: + if "deleted" in str(playval): + if existing: + cmd = "no " + ref[k]["setval"].format(existing) + else: + cmd = ref[k]["setval"].format(playval) + else: + raise ValueError("get_proposed: unknown 'kind' value specified for key '{0}'".format(k)) + if cmd: + if ref["_state"] in self.absent_states and not re.search(r"^no", cmd): + cmd = "no " + cmd + # Commands may require parent commands for proper context. + # Global _template context is replaced by parameter context + [proposed.append(ctx) for ctx in ref["_context"]] + [proposed.append(ctx) for ctx in ref[k].get("context", [])] + proposed.append(cmd) + + def get_proposed(self): + """Compare playbook values against existing states and create a list + of proposed commands. + Return a list of raw cli command strings. + """ + ref = self._ref + # '_proposed' may be empty list or contain initializations; e.g. ['feature foo'] + proposed = ref["_proposed"] + + if ref["_context"] and ref["_context"][-1].startswith("no"): + [proposed.append(ctx) for ctx in ref["_context"]] + return proposed + + # Create a list of commands that have playbook values + play_keys = [k for k in ref["commands"] if "playval" in ref[k]] + + def compare(playval, existing): + if ref["_state"] in self.present_states: + if existing is None: + return False + elif str(playval) == str(existing): + return True + elif isinstance(existing, dict) and playval in existing.values(): + return True + + if ref["_state"] in self.absent_states: + if isinstance(existing, dict) and all(x is None for x in existing.values()): + existing = None + if existing is None or playval not in existing.values(): + return True + return False + + # Compare against current state + for k in play_keys: + playval = ref[k]["playval"] + # Create playval copy to avoid RuntimeError + # dictionary changed size during iteration error + playval_copy = deepcopy(playval) + existing = ref[k].get("existing", ref[k]["default"]) + multiple = "multiple" in ref[k].keys() + + # Multiple Instances: + if isinstance(existing, dict) and multiple: + for ekey, evalue in existing.items(): + if isinstance(evalue, dict): + # Remove values set to string 'None' from dvalue + evalue = dict((k, v) for k, v in evalue.items() if v != "None") + for pkey, pvalue in playval.items(): + if compare(pvalue, evalue): + if playval_copy.get(pkey): + del playval_copy[pkey] + if not playval_copy: + continue + # Single Instance: + else: + for pkey, pval in playval.items(): + if compare(pval, existing): + if playval_copy.get(pkey): + del playval_copy[pkey] + if not playval_copy: + continue + + playval = playval_copy + # Multiple Instances: + if isinstance(existing, dict): + for dkey, dvalue in existing.items(): + for pval in playval.values(): + self.build_cmd_set(pval, dvalue, k) + # Single Instance: + else: + for pval in playval.values(): + self.build_cmd_set(pval, existing, k) + + # Remove any duplicate commands before returning. + # pylint: disable=unnecessary-lambda + cmds = sorted(set(proposed), key=lambda x: proposed.index(x)) + return cmds + + +def nxosCmdRef_import_check(): + """Return import error messages or empty string""" + msg = "" + if not HAS_YAML: + msg += "Mandatory python library 'PyYAML' is not present, try 'pip install PyYAML'\n" + return msg + + +def is_json(cmd): + return to_text(cmd).endswith("| json") + + +def is_text(cmd): + return not is_json(cmd) + + +def to_command(module, commands): + transform = ComplexList( + dict( + command=dict(key=True), + output=dict(type="str", default="text"), + 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), + ), + module, + ) + + commands = transform(to_list(commands)) + + for item in commands: + if is_json(item["command"]): + item["output"] = "json" + + return commands + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + conn = get_connection(module) + return conn.get_config(flags=flags) + + +def run_commands(module, commands, check_rc=True): + conn = get_connection(module) + return conn.run_commands(to_command(module, commands), check_rc) + + +def load_config(module, config, return_error=False, opts=None, replace=None): + conn = get_connection(module) + return conn.load_config(config, return_error, opts, replace=replace) + + +def get_capabilities(module): + conn = get_connection(module) + return conn.get_capabilities() + + +def get_diff( + self, + candidate=None, + running=None, + diff_match="line", + diff_ignore_lines=None, + path=None, + diff_replace="line", +): + conn = self.get_connection() + return conn.get_diff( + candidate=candidate, + running=running, + diff_match=diff_match, + diff_ignore_lines=diff_ignore_lines, + path=path, + diff_replace=diff_replace, + ) + + +def normalize_interface(name): + """Return the normalized interface name""" + if not name: + return + + def _get_number(name): + digits = "" + for char in name: + if char.isdigit() or char in "/.": + digits += char + return digits + + if name.lower().startswith("et"): + if_type = "Ethernet" + elif name.lower().startswith("vl"): + if_type = "Vlan" + elif name.lower().startswith("lo"): + if_type = "loopback" + elif name.lower().startswith("po"): + if_type = "port-channel" + elif name.lower().startswith("nv"): + if_type = "nve" + else: + if_type = None + + number_list = name.split(" ") + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + + +def get_interface_type(interface): + """Gets the type of interface""" + if interface.upper().startswith("ET"): + return "ethernet" + elif interface.upper().startswith("VL"): + return "svi" + elif interface.upper().startswith("LO"): + return "loopback" + elif interface.upper().startswith("MG"): + return "management" + elif interface.upper().startswith("MA"): + return "management" + elif interface.upper().startswith("PO"): + return "portchannel" + elif interface.upper().startswith("NV"): + return "nve" + else: + return "unknown" + + +def default_intf_enabled(name="", sysdefs=None, mode=None): + """Get device/version/interface-specific default 'enabled' state. + L3: + - Most L3 intfs default to 'shutdown'. Loopbacks default to 'no shutdown'. + - Some legacy platforms default L3 intfs to 'no shutdown'. + L2: + - User-System-Default 'system default switchport shutdown' defines the + enabled state for L2 intf's. USD defaults may be different on some platforms. + - An intf may be explicitly defined as L2 with 'switchport' or it may be + implicitly defined as L2 when USD 'system default switchport' is defined. + """ + if not name: + return None + if sysdefs is None: + sysdefs = {} + default = False + + if re.search("port-channel|loopback", name): + default = True + else: + if mode is None: + # intf 'switchport' cli is not present so use the user-system-default + mode = sysdefs.get("mode") + + if mode == "layer3": + default = sysdefs.get("L3_enabled") + elif mode == "layer2": + default = sysdefs.get("L2_enabled") + return default + + +def read_module_context(module): + conn = get_connection(module) + return conn.read_module_context(module._name) + + +def save_module_context(module, module_context): + conn = get_connection(module) + return conn.save_module_context(module._name, module_context) diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_address_family.py new file mode 100644 index 00000000..942e993e --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_address_family.py @@ -0,0 +1,798 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Bgp_address_family parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_aggregate_address(aggaddr): + cmd = "aggregate-address {prefix}" + + if aggaddr.get("advertise_map"): + cmd += " advertise-map {advertise_map}" + if aggaddr.get("as_set"): + cmd += " as-set" + if aggaddr.get("attribute_map"): + cmd += " attribute-map {attribute_map}" + if aggaddr.get("summary_only"): + cmd += " summary-only" + if aggaddr.get("suppress_map"): + cmd += " suppress-map {suppress_map}" + + return cmd.format(**aggaddr) + + +def _tmplt_dampening(proc): + damp = proc.get("dampening", {}) + cmd = "dampening" + + if damp.get("set") is False: + return "no {0}".format(cmd) + if damp.get("route_map"): + cmd += " route-map {route_map}".format(**damp) + for x in ( + "decay_half_life", + "start_reuse_route", + "start_suppress_route", + "max_suppress_time", + ): + if x in damp: + cmd += " {0}".format(damp[x]) + return cmd + + +def _tmplt_redistribute(redis): + command = "redistribute {protocol}".format(**redis) + if redis.get("id"): + command += " {id}".format(**redis) + command += " route-map {route_map}".format(**redis) + return command + + +class Bgp_address_familyTemplate(NetworkTemplate): + def __init__(self, lines=None): + super(Bgp_address_familyTemplate, self).__init__(lines=lines, tmplt=self) + + # fmt: off + PARSERS = [ + { + "name": "as_number", + "getval": re.compile( + r""" + ^router\sbgp\s(?P<as_number>\S+) + $""", re.VERBOSE, + ), + "setval": "router bgp {{ as_number }}", + "result": { + "as_number": "{{ as_number }}", + }, + "shared": True, + }, + { + "name": "address_family", + "getval": re.compile( + r""" + (\s+vrf\s(?P<vrf>\S+))? + (\s+neighbor\s(?P<nbr>\S+))? + \s+address-family + \s(?P<afi>\S+) + (\s(?P<safi>\S+))? + $""", + re.VERBOSE, + ), + "setval": "address-family {{ afi }}{{ (' ' + safi) if safi is defined else ''}}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "vrf": "{{ vrf }}", + "afi": "{{ afi }}", + "safi": "{{ safi }}", + }, + }, + }, + "shared": True, + }, + { + "name": "additional_paths.install_backup", + "getval": re.compile( + r""" + \s+additional-paths + \sinstall\s(?P<backup>backup) + $""", + re.VERBOSE, + ), + "setval": "additional-paths install backup", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "additional_paths": { + "install_backup": "{{ not not backup }}", + }, + }, + }, + }, + }, + { + "name": "additional_paths.receive", + "getval": re.compile( + r""" + \s+additional-paths + \s(?P<receive>receive) + $""", + re.VERBOSE, + ), + "setval": "additional-paths receive", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "additional_paths": { + "receive": "{{ not not receive }}", + }, + }, + }, + }, + }, + { + "name": "additional_paths.selection.route_map", + "getval": re.compile( + r""" + \s+additional-paths + \sselection\sroute-map + \s(?P<route_map>\S+) + $""", + re.VERBOSE, + ), + "setval": "additional-paths selection route-map {{ additional_paths.selection.route_map }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "additional_paths": { + "selection": { + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + }, + { + "name": "additional_paths.send", + "getval": re.compile( + r""" + \s+additional-paths + \s(?P<send>send) + $""", + re.VERBOSE, + ), + "setval": "additional-paths send", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "additional_paths": { + "send": "{{ not not send }}", + }, + }, + }, + }, + }, + { + "name": "advertise_l2vpn_evpn", + "getval": re.compile( + r""" + \s+(?P<advertise_l2vpn_evpn>advertise\sl2vpn\sevpn) + $""", + re.VERBOSE, + ), + "setval": "advertise l2vpn evpn", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "advertise_l2vpn_evpn": "{{ not not advertise_l2vpn_evpn }}", + }, + }, + }, + }, + { + "name": "advertise_pip", + "getval": re.compile( + r""" + \s+(?P<advertise_pip>advertise-pip) + $""", + re.VERBOSE, + ), + "setval": "advertise-pip", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "advertise_pip": "{{ not not advertise_pip }}", + }, + }, + }, + }, + { + "name": "advertise_system_mac", + "getval": re.compile( + r""" + \s+(?P<advertise_system_mac>advertise-system-mac) + $""", + re.VERBOSE, + ), + "setval": "advertise-system-mac", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "advertise_system_mac": "{{ not not advertise_system_mac }}", + }, + }, + }, + }, + { + "name": "allow_vni_in_ethertag", + "getval": re.compile( + r""" + \s+(?P<allow_vni_in_ethertag>allow-vni-in-ethertag) + $""", + re.VERBOSE, + ), + "setval": "allow-vni-in-ethertag", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "allow_vni_in_ethertag": "{{ not not allow_vni_in_ethertag }}", + }, + }, + }, + }, + { + "name": "aggregate_address", + "getval": re.compile( + r""" + \s+aggregate-address + \s(?P<prefix>\S+) + (\s(?P<as_set>as-set))? + (\s(?P<summary_only>summary-only))? + (\sadvertise-map\s(?P<advertise_map>\S+))? + (\sattribute-map\s(?P<attribute_map>\S+))? + (\ssuppress-map\s(?P<suppress_map>\S+))? + $""", + re.VERBOSE, + ), + "setval": _tmplt_aggregate_address, + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "aggregate_address": [ + { + "prefix": "{{ prefix }}", + "as_set": "{{ True if as_set is defined else None }}", + "summary_only": "{{ True if summary_only is defined else None }}", + "advertise_map": "{{ advertise_map }}", + "attribute_map": "{{ attribute_map }}", + "suppress_map": "{{ suppress_map }}", + }, + ], + }, + }, + }, + }, + { + "name": "client_to_client.no_reflection", + "getval": re.compile( + r""" + \s+no\sclient-to-client + \s(?P<reflection>reflection) + $""", + re.VERBOSE, + ), + "setval": "client-to-client reflection", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "client_to_client": { + "no_reflection": "{{ not not reflection }}", + }, + }, + }, + }, + }, + { + "name": "dampen_igp_metric", + "getval": re.compile( + r""" + \s+dampen-igp-metric + \s(?P<dampen_igp_metric>\d+) + $""", + re.VERBOSE, + ), + "setval": "dampen-igp-metric {{ dampen_igp_metric }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "dampen_igp_metric": "{{ dampen_igp_metric }}", + }, + }, + }, + }, + { + "name": "dampening", + "getval": re.compile( + r""" + \s+(?P<dampening>dampening) + (\s(?P<decay_half_life>\d+))? + (\s(?P<start_reuse_route>\d+))? + (\s(?P<start_suppress_route>\d+))? + (\s(?P<max_suppress_time>\d+))? + (\sroute-map\s(?P<route_map>\S+))? + $""", + re.VERBOSE, + ), + "setval": _tmplt_dampening, + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "dampening": { + "set": "{{ True if dampening is defined" + " and ((not decay_half_life|d(False)," + " not start_reuse_route|d(False), " + " not start_suppress_route|d(False), not max_suppress_time|d(False), not route_map|d(""))|all) }}", + "decay_half_life": "{{ decay_half_life }}", + "start_reuse_route": "{{ start_reuse_route }}", + "start_suppress_route": "{{ start_suppress_route }}", + "max_suppress_time": "{{ max_suppress_time }}", + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + { + "name": "default_information.originate", + "getval": re.compile( + r""" + \s+default-information + \s(?P<originate>originate) + $""", + re.VERBOSE, + ), + "setval": "default-information originate", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "default_information": { + "originate": "{{ not not originate }}", + }, + }, + }, + }, + }, + { + "name": "default_metric", + "getval": re.compile( + r""" + \s+default-metric + \s(?P<default_metric>\d+) + $""", + re.VERBOSE, + ), + "setval": "default-metric {{ default_metric }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "default_metric": "{{ default_metric }}", + }, + }, + }, + }, + { + "name": "distance", + "getval": re.compile( + r""" + \s+distance + \s(?P<ebgp_routes>\d+) + \s(?P<ibgp_routes>\d+) + \s(?P<local_routes>\d+) + $""", + re.VERBOSE, + ), + "setval": "distance {{ distance.ebgp_routes }} {{ distance.ibgp_routes }} {{ distance.local_routes }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "distance": { + "ebgp_routes": "{{ ebgp_routes }}", + "ibgp_routes": "{{ ibgp_routes }}", + "local_routes": "{{ local_routes }}", + }, + }, + }, + }, + }, + { + "name": "export_gateway_ip", + "getval": re.compile( + r""" + \s+(?P<export_gateway_ip>export-gateway-ip) + $""", + re.VERBOSE, + ), + "setval": "export-gateway-ip", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "export_gateway_ip": "{{ not not export_gateway_ip }}", + }, + }, + }, + }, + { + "name": "inject_map", + "getval": re.compile( + r""" + \s+inject-map + \s(?P<route_map>\S+) + \sexist-map\s(?P<exist_map>\S+) + (\s(?P<copy_attributes>copy-attributes))? + $""", + re.VERBOSE, + ), + "setval": "inject-map {{ route_map }} exist-map {{ exist_map }}{{ ' copy-attributes' if copy_attributes|d(False) else '' }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "inject_map": [ + { + "route_map": "{{ route_map }}", + "exist_map": "{{ exist_map }}", + "copy_attributes": "{{ not not copy_attributes }}", + }, + ], + }, + }, + }, + }, + { + "name": "maximum_paths.parallel_paths", + "getval": re.compile( + r""" + \s+maximum-paths + \s(?P<parallel_paths>\d+) + $""", + re.VERBOSE, + ), + "setval": "maximum-paths {{ maximum_paths.parallel_paths }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "maximum_paths": { + "parallel_paths": "{{ parallel_paths }}", + }, + }, + }, + }, + }, + { + "name": "maximum_paths.ibgp.parallel_paths", + "getval": re.compile( + r""" + \s+maximum-paths + \sibgp\s(?P<parallel_paths>\d+) + $""", + re.VERBOSE, + ), + "setval": "maximum-paths ibgp {{ maximum_paths.ibgp.parallel_paths }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "maximum_paths": { + "ibgp": { + "parallel_paths": "{{ parallel_paths }}", + }, + }, + }, + }, + }, + }, + { + "name": "maximum_paths.eibgp.parallel_paths", + "getval": re.compile( + r""" + \s+maximum-paths + \seibgp\s(?P<parallel_paths>\d+) + $""", + re.VERBOSE, + ), + "setval": "maximum-paths eibgp {{ maximum_paths.eibgp.parallel_paths }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "maximum_paths": { + "eibgp": { + "parallel_paths": "{{ parallel_paths }}", + }, + }, + }, + }, + }, + }, + { + "name": "maximum_paths.local.parallel_paths", + "getval": re.compile( + r""" + \s+maximum-paths + \slocal\s(?P<parallel_paths>\d+) + $""", + re.VERBOSE, + ), + "setval": "maximum-paths local {{ maximum_paths.local.parallel_paths }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "maximum_paths": { + "local": { + "parallel_paths": "{{ parallel_paths }}", + }, + }, + }, + }, + }, + }, + { + "name": "maximum_paths.mixed.parallel_paths", + "getval": re.compile( + r""" + \s+maximum-paths + \smixed\s(?P<parallel_paths>\d+) + $""", + re.VERBOSE, + ), + "setval": "maximum-paths mixed {{ maximum_paths.mixed.parallel_paths }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "maximum_paths": { + "mixed": { + "parallel_paths": "{{ parallel_paths }}", + }, + }, + }, + }, + }, + }, + { + "name": "networks", + "getval": re.compile( + r""" + \s+network + \s(?P<prefix>\S+) + (\sroute-map\s(?P<route_map>\S+))? + $""", + re.VERBOSE, + ), + "setval": "network {{ prefix }}{{ (' route-map ' + route_map) if route_map is defined else '' }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "networks": [ + { + "prefix": "{{ prefix }}", + "route_map": "{{ route_map }}", + }, + ], + }, + }, + }, + }, + { + "name": "nexthop.route_map", + "getval": re.compile( + r""" + \s+nexthop + \sroute-map\s(?P<route_map>\S+) + $""", + re.VERBOSE, + ), + "setval": "nexthop route-map {{ nexthop.route_map }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "nexthop": { + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + { + "name": "nexthop.trigger_delay", + "getval": re.compile( + r""" + \s+nexthop + \strigger-delay + \scritical\s(?P<critical_delay>\d+) + \snon-critical\s(?P<non_critical_delay>\d+) + $""", + re.VERBOSE, + ), + "setval": "nexthop trigger-delay critical {{ nexthop.trigger_delay.critical_delay }} non-critical {{ nexthop.trigger_delay.non_critical_delay }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "nexthop": { + "trigger_delay": { + "critical_delay": "{{ critical_delay }}", + "non_critical_delay": "{{ non_critical_delay }}", + }, + }, + }, + }, + }, + }, + { + "name": "redistribute", + "getval": re.compile( + r""" + \s+redistribute + \s(?P<protocol>\S+) + (\s(?P<id>\S+))? + \sroute-map\s(?P<rmap>\S+) + $""", + re.VERBOSE, + ), + "setval": _tmplt_redistribute, + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "redistribute": [ + { + "protocol": "{{ protocol }}", + "id": "{{ id }}", + "route_map": "{{ rmap }}", + }, + ], + }, + }, + }, + }, + { + "name": "retain.route_target.retain_all", + "getval": re.compile( + r""" + \s+retain\sroute-target + \s(?P<retain_all>all) + $""", + re.VERBOSE, + ), + "setval": "retain route-target all", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "retain": { + "route_target": { + "retain_all": "{{ not not retain_all }}", + }, + }, + }, + }, + }, + }, + { + "name": "retain.route_target.route_map", + "getval": re.compile( + r""" + \s+retain\sroute-target + \sroute-map\s(?P<route_map>\S+) + $""", + re.VERBOSE, + ), + "setval": "retain route-target route-map {{ retain.route_target.route_map }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "retain": { + "route_target": { + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + }, + { + "name": "suppress_inactive", + "getval": re.compile( + r""" + \s+(?P<suppress_inactive>suppress-inactive) + $""", + re.VERBOSE, + ), + "setval": "suppress-inactive", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "suppress_inactive": "{{ not not suppress_inactive }}", + }, + }, + }, + }, + { + "name": "table_map", + "getval": re.compile( + r""" + \s+table-map + \s(?P<name>\S+) + (\s(?P<filter>filter))? + $""", + re.VERBOSE, + ), + "setval": "table-map {{ table_map.name }}{{ ' filter' if table_map.filter|d(False) }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "table_map": { + "name": "{{ name }}", + "filter": "{{ not not filter }}", + }, + }, + }, + }, + }, + { + "name": "timers.bestpath_defer", + "getval": re.compile( + r""" + \s+timers + \sbestpath-defer\s(?P<defer_time>\d+) + \smaximum\s(?P<maximum_defer_time>\d+) + $""", + re.VERBOSE, + ), + "setval": "timers bestpath-defer {{ timers.bestpath_defer.defer_time }} maximum {{ timers.bestpath_defer.maximum_defer_time }}", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "timers": { + "bestpath_defer": { + "defer_time": "{{ defer_time }}", + "maximum_defer_time": "{{ maximum_defer_time }}", + }, + }, + }, + }, + }, + }, + { + "name": "wait_igp_convergence", + "getval": re.compile( + r""" + \s+(?P<wait_igp_convergence>wait-igp-convergence) + $""", + re.VERBOSE, + ), + "setval": "wait-igp-convergence", + "result": { + "address_family": { + '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': { + "wait_igp_convergence": "{{ not not wait_igp_convergence }}", + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_global.py new file mode 100644 index 00000000..fa99397a --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_global.py @@ -0,0 +1,1536 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Bgp_global parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_confederation_peers(proc): + cmd = "confederation peers" + for peer in proc.get("confederation", {})["peers"]: + cmd += " {0}".format(peer) + return cmd + + +def _tmplt_path_attribute(proc): + cmd = "path-attribute {action}".format(**proc) + + if "type" in proc: + cmd += " {type}".format(**proc) + elif "range" in proc: + cmd += " range {start} {end}".format(**proc["range"]) + cmd += " in" + + return cmd + + +def _tmplt_bfd(proc): + bfd = proc.get("bfd", {}) + cmd = None + + if bfd.get("set"): + cmd = "bfd" + if bfd.get("singlehop"): + cmd = "bfd singlehop" + elif bfd.get("multihop", {}).get("set"): + cmd = "bfd multihop" + + if cmd: + return cmd + + +class Bgp_globalTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Bgp_globalTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "as_number", + "getval": re.compile( + r""" + ^router\sbgp\s(?P<as_number>\S+) + $""", re.VERBOSE, + ), + "setval": "router bgp {{ as_number }}", + "result": { + "as_number": "{{ as_number }}", + }, + "shared": True, + }, + { + "name": "vrf", + "getval": re.compile( + r""" + \s+vrf + \s(?P<vrf>\S+)$""", + re.VERBOSE, + ), + "setval": "vrf {{ vrf }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "vrf": "{{ vrf }}", + }, + }, + }, + "shared": True, + }, + { + "name": "affinity_group.group_id", + "getval": re.compile( + r""" + \s+affinity-group + \sactivate\s(?P<group_id>\S+) + $""", re.VERBOSE, + ), + "setval": "affinity-group activate {{ affinity_group.group_id }}", + "result": { + "affinity_group": { + "group_id": "{{ group_id }}", + }, + }, + }, + { + "name": "bestpath.always_compare_med", + "getval": re.compile( + r""" + \s+bestpath\s(?P<always_compare_med>always-compare-med) + $""", re.VERBOSE, + ), + "setval": "bestpath always-compare-med", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "always_compare_med": "{{ not not always_compare_med }}", + }, + }, + }, + }, + }, + { + "name": "bestpath.as_path.ignore", + "getval": re.compile( + r""" + \s+bestpath\sas-path\s(?P<ignore>ignore) + $""", re.VERBOSE, + ), + "setval": "bestpath as-path ignore", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "as_path": { + "ignore": "{{ not not ignore }}", + }, + }, + }, + }, + }, + }, + { + "name": "bestpath.as_path.multipath_relax", + "getval": re.compile( + r""" + \s+bestpath\sas-path\s(?P<multipath_relax>multipath-relax) + $""", re.VERBOSE, + ), + "setval": "bestpath as-path multipath-relax", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "as_path": { + "multipath_relax": "{{ not not multipath_relax }}", + }, + }, + }, + }, + }, + }, + { + "name": "bestpath.compare_neighborid", + "getval": re.compile( + r""" + \s+bestpath\s(?P<compare_neighborid>compare-neighborid) + $""", re.VERBOSE, + ), + "setval": "bestpath compare-neighborid", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "compare_neighborid": "{{ not not compare_neighborid }}", + }, + }, + }, + + }, + }, + { + "name": "bestpath.compare_routerid", + "getval": re.compile( + r""" + \s+bestpath\s(?P<compare_routerid>compare-routerid) + $""", re.VERBOSE, + ), + "setval": "bestpath compare-routerid", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "compare_routerid": "{{ not not compare_routerid }}", + }, + }, + }, + }, + }, + { + "name": "bestpath.cost_community_ignore", + "getval": re.compile( + r""" + \s+bestpath\scost-community\s(?P<cost_community_ignore>ignore) + $""", re.VERBOSE, + ), + "setval": "bestpath cost-community ignore", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "cost_community_ignore": "{{ not not cost_community_ignore }}", + }, + }, + }, + }, + }, + { + "name": "bestpath.igp_metric_ignore", + "getval": re.compile( + r""" + \s+bestpath\sigp-metric\s(?P<igp_metric_ignore>ignore) + $""", re.VERBOSE, + ), + "setval": "bestpath igp-metric ignore", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "igp_metric_ignore": "{{ not not igp_metric_ignore }}", + }, + }, + }, + }, + }, + { + "name": "bestpath.med.confed", + "getval": re.compile( + r""" + \s+bestpath\smed\s(?P<confed>confed) + $""", re.VERBOSE, + ), + "setval": "bestpath med confed", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "med": { + "confed": "{{ not not confed }}", + }, + }, + }, + }, + }, + }, + { + "name": "bestpath.med.missing_as_worst", + "getval": re.compile( + r""" + \s+bestpath\smed\s(?P<missing_as_worst>missing-as-worst) + $""", re.VERBOSE, + ), + "setval": "bestpath med missing-as-worst", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "med": { + "missing_as_worst": "{{ not not missing_as_worst }}", + }, + }, + }, + }, + }, + }, + { + "name": "bestpath.med.non_deterministic", + "getval": re.compile( + r""" + \s+bestpath\smed\s(?P<non_deterministic>non-deterministic) + $""", re.VERBOSE, + ), + "setval": "bestpath med non-deterministic", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bestpath": { + "med": { + "non_deterministic": "{{ not not non_deterministic }}", + }, + }, + }, + }, + }, + }, + { + "name": "cluster_id", + "getval": re.compile( + r""" + \s+cluster-id\s(?P<cluster_id>\S+) + $""", re.VERBOSE, + ), + "setval": "cluster-id {{ cluster_id }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "cluster_id": "{{ cluster_id }}", + }, + }, + }, + }, + { + "name": "confederation.identifier", + "getval": re.compile( + r""" + \s+confederation\sidentifier\s(?P<identifier>\S+) + $""", re.VERBOSE, + ), + "setval": "confederation identifier {{ confederation.identifier }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "confederation": { + "identifier": "{{ identifier }}", + }, + }, + }, + }, + }, + { + "name": "confederation.peers", + "getval": re.compile( + r""" + \s+confederation\speers\s(?P<peers>.*) + $""", re.VERBOSE, + ), + "setval": _tmplt_confederation_peers, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "confederation": { + "peers": "{{ peers }}", + }, + }, + }, + }, + }, + { + "name": "disable_policy_batching", + "getval": re.compile( + r""" + \s+(?P<disable_policy_batching>disable-policy-batching) + $""", re.VERBOSE, + ), + "setval": "disable-policy-batching", + "result": { + "disable_policy_batching": { + "set": "{{ not not disable_policy_batching }}", + }, + }, + }, + { + "name": "disable_policy_batching.ipv4.prefix_list", + "getval": re.compile( + r""" + \s+disable-policy-batching\sipv4 + \sprefix-list\s(?P<ipv4_prefix_list>\S+) + $""", re.VERBOSE, + ), + "setval": "disable-policy-batching ipv4 prefix-list {{ disable_policy_batching.ipv4.prefix_list }}", + "result": { + "disable_policy_batching": { + "ipv4": { + "prefix_list": "{{ ipv4_prefix_list }}", + }, + }, + }, + }, + { + "name": "disable_policy_batching.ipv6.prefix_list", + "getval": re.compile( + r""" + \s+disable-policy-batching\sipv6 + \sprefix-list\s(?P<ipv6_prefix_list>\S+) + $""", re.VERBOSE, + ), + "setval": "disable-policy-batching ipv6 prefix-list {{ disable_policy_batching.ipv6.prefix_list }}", + "result": { + "disable_policy_batching": { + "ipv6": { + "prefix_list": "{{ ipv6_prefix_list }}", + }, + }, + }, + }, + { + "name": "disable_policy_batching.nexthop", + "getval": re.compile( + r""" + \s+disable-policy-batching\s(?P<nexthop>nexthop) + $""", re.VERBOSE, + ), + "setval": "disable-policy-batching nexthop", + "result": { + "disable_policy_batching": { + "nexthop": "{{ not not nexthop }}", + }, + }, + }, + { + "name": "dynamic_med_interval", + "getval": re.compile( + r""" + \s+dynamic-med-interval\s(?P<dynamic_med_interval>\d+) + $""", re.VERBOSE, + ), + "setval": "dynamic-med-interval {{ dynamic_med_interval }}", + "result": { + "dynamic_med_interval": "{{ dynamic_med_interval }}", + }, + }, + { + "name": "enforce_first_as", + "getval": re.compile( + r""" + \s+no\s(?P<enforce_first_as>enforce-first-as) + $""", re.VERBOSE, + ), + "setval": "enforce-first-as", + "result": { + "enforce_first_as": "{{ not enforce_first_as }}", + }, + }, + { + "name": "enhanced_error", + "getval": re.compile( + r""" + \s+no\s(?P<enhanced_error>enhanced-error) + $""", re.VERBOSE, + ), + "setval": "enhanced-error", + "result": { + "enhanced_error": "{{ not enhanced_error }}", + }, + }, + { + "name": "fast_external_fallover", + "getval": re.compile( + r""" + \s+no\s(?P<fast_external_fallover>fast-external-fallover) + $""", re.VERBOSE, + ), + "setval": "fast-external-fallover", + "result": { + "fast_external_fallover": "{{ not fast_external_fallover }}", + }, + }, + { + "name": "flush_routes", + "getval": re.compile( + r""" + \s+(?P<flush_routes>flush-routes) + $""", re.VERBOSE, + ), + "setval": "flush-routes", + "result": { + "flush_routes": "{{ not not flush_routes }}", + }, + }, + { + "name": "graceful_restart", + "getval": re.compile( + r""" + \s+no\s(?P<graceful_restart>graceful-restart) + $""", re.VERBOSE, + ), + "setval": "graceful-restart", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "set": "{{ not graceful_restart }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart.restart_time", + "getval": re.compile( + r""" + \s+graceful-restart\srestart-time\s(?P<restart_time>\d+) + $""", re.VERBOSE, + ), + "setval": "graceful-restart restart-time {{ graceful_restart.restart_time }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "restart_time": "{{ restart_time }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart.stalepath_time", + "getval": re.compile( + r""" + \s+graceful-restart\sstalepath-time\s(?P<stalepath_time>\d+) + $""", re.VERBOSE, + ), + "setval": "graceful-restart stalepath-time {{ graceful_restart.stalepath_time }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "stalepath_time": "{{ stalepath_time }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart.helper", + "getval": re.compile( + r""" + \s+(?P<helper>graceful-restart-helper) + $""", re.VERBOSE, + ), + "setval": "graceful-restart-helper", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "helper": "{{ not not helper }}", + }, + }, + }, + }, + }, + { + "name": "graceful_shutdown.activate", + "getval": re.compile( + r""" + \s+graceful-shutdown + \s(?P<activate>activate) + (\sroute-map + \s(?P<route_map>\S+))? + $""", re.VERBOSE, + ), + "setval": "graceful-shutdown activate{{ ' route-map ' + graceful_shutdown.activate.route_map if graceful_shutdown.activate.route_map is defined }}", + "result": { + "graceful_shutdown": { + "activate": { + "set": "{{ True if activate is defined and route_map is undefined else None }}", + "route_map": "{{ route_map }}", + }, + }, + }, + }, + { + "name": "graceful_shutdown.aware", + "getval": re.compile( + r""" + \s+no\sgraceful-shutdown + \s(?P<aware>aware) + $""", re.VERBOSE, + ), + "setval": "graceful-shutdown aware", + "result": { + "graceful_shutdown": { + "aware": "{{ not aware }}", + }, + }, + }, + { + "name": "isolate", + "getval": re.compile( + r""" + \s+(?P<isolate>isolate) + (\s(?P<include_local>include-local))? + $""", re.VERBOSE, + ), + "setval": "isolate{{ ' include-local' if isolate.include_local|d(False) is True }}", + "result": { + "isolate": { + "set": "{{ True if isolate is defined and include_local is not defined else None }}", + "include_local": "{{ not not include_local }}", + }, + }, + }, + { + "name": "log_neighbor_changes", + "getval": re.compile( + r""" + \s+(?P<log_neighbor_changes>log-neighbor-changes) + $""", re.VERBOSE, + ), + "setval": "log-neighbor-changes", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "log_neighbor_changes": "{{ not not log_neighbor_changes }}", + }, + }, + }, + }, + { + "name": "maxas_limit", + "getval": re.compile( + r""" + \s+maxas-limit\s(?P<maxas_limit>\d+) + $""", re.VERBOSE, + ), + "setval": "maxas-limit {{ maxas_limit }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "maxas_limit": "{{ maxas_limit }}", + }, + }, + }, + }, + # start neighbor parsers + { + "name": "neighbor_address", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + $""", re.VERBOSE, + ), + "setval": "neighbor {{ neighbor_address }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "neighbor_address": "{{ neighbor_address }}", + }, + }, + }, + }, + }, + }, + { + "name": "bfd", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \s(?P<bfd>bfd) + (\s(?P<singlehop>singlehop))? + (\s(?P<multihop>multihop))? + $""", re.VERBOSE, + ), + "setval": _tmplt_bfd, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "bfd": { + "set": "{{ True if bfd is defined and singlehop is undefined and multihop is undefined else None }}", + "singlehop": "{{ not not singlehop }}", + "multihop": { + "set": "{{ not not multihop }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "bfd.multihop.interval", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sbfd\smultihop\sinterval + \s(?P<tx_interval>\d+) + \smin_rx\s(?P<min_rx_interval>\d+) + \smultiplier\s(?P<multiplier>\d+) + $""", re.VERBOSE, + ), + "setval": "bfd multihop interval" + " {{ bfd.multihop.interval.tx_interval }}" + " min_rx {{ bfd.multihop.interval.min_rx_interval }}" + " multiplier {{ bfd.multihop.interval.multiplier }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "bfd": { + "multihop": { + "interval": { + "tx_interval": "{{ tx_interval }}", + "min_rx_interval": "{{ min_rx_interval }}", + "multiplier": "{{ multiplier }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "remote_as", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sremote-as\s(?P<remote_as>\S+) + $""", re.VERBOSE, + ), + "setval": "remote-as {{ remote_as }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "remote_as": "{{ remote_as }}", + }, + }, + }, + }, + }, + }, + { + "name": "neighbor_affinity_group.group_id", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \saffinity-group\s(?P<group_id>\d+) + $""", re.VERBOSE, + ), + "setval": "affinity-group {{ neighbor_affinity_group.group_id }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "neighbor_affinity_group": { + "group_id": "{{ group_id }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "bmp_activate_server", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sbmp-activate-server\s(?P<bmp_activate_server>\d+) + $""", re.VERBOSE, + ), + "setval": "bmp-activate-server {{ bmp_activate_server }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "bmp_activate_server": "{{ bmp_activate_server }}", + }, + }, + }, + }, + }, + }, + { + "name": "capability", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \scapability\ssuppress\s(?P<suppress_4_byte_as>4-byte-as) + $""", re.VERBOSE, + ), + "setval": "capability suppress 4-byte-as", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "capability": { + "suppress_4_byte_as": "{{ not not suppress_4_byte_as }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "description", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sdescription\s(?P<description>\S+) + $""", re.VERBOSE, + ), + "setval": "description {{ description }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "description": "{{ description }}", + }, + }, + }, + }, + }, + }, + { + "name": "disable_connected_check", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \s(?P<disable_connected_check>disable-connected-check) + $""", re.VERBOSE, + ), + "setval": "disable-connected-check", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "disable_connected_check": "{{ not not disable_connected_check }}", + }, + }, + }, + }, + }, + }, + { + "name": "dont_capability_negotiate", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \s(?P<dont_capability_negotiate>dont-capability-negotiate) + $""", re.VERBOSE, + ), + "setval": "dont-capability-negotiate", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "dont_capability_negotiate": "{{ not not dont_capability_negotiate}}", + }, + }, + }, + }, + }, + }, + { + "name": "dscp", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sdscp\s(?P<dscp>\S+) + $""", re.VERBOSE, + ), + "setval": "dscp {{ dscp }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "dscp": "{{ dscp }}", + }, + }, + }, + }, + }, + }, + { + "name": "dynamic_capability", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \s(?P<dynamic_capability>dynamic-capability) + $""", re.VERBOSE, + ), + "setval": "dynamic-capability", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "dynamic_capability": "{{ not not dynamic_capability }}", + }, + }, + }, + }, + }, + }, + { + "name": "ebgp_multihop", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sebgp-multihop\s(?P<ebgp_multihop>\d+) + $""", re.VERBOSE, + ), + "setval": "ebgp-multihop {{ ebgp_multihop }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "ebgp_multihop": "{{ ebgp_multihop }}", + }, + }, + }, + }, + }, + }, + { + "name": "graceful_shutdown", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sgraceful-shutdown + \s(?P<activate>activate) + (\sroute-map\s(?P<route_map>\S+))? + $""", re.VERBOSE, + ), + "setval": "graceful-shutdown{{ (' route-map ' + graceful_shutdown.route_map) if graceful_shutdown.route_map is defined }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "graceful_shutdown": { + "activate": { + "set": "{{ True if activate is defined and route_map is undefined else None }}", + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "inherit.peer", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sinherit + \speer\s(?P<peer>\S+) + $""", re.VERBOSE, + ), + "setval": "inherit peer {{ inherit.peer }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "inherit": { + "peer": "{{ peer }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "inherit.peer_session", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sinherit + \speer-session\s(?P<peer_session>\S+) + $""", re.VERBOSE, + ), + "setval": "inherit peer-session {{ inherit.peer_session }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "inherit": { + "peer_session": "{{ peer_session }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "local_as", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \slocal-as\s(?P<local_as>\S+) + $""", re.VERBOSE, + ), + "setval": "local-as {{ local_as }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "local_as": "{{ local_as }}", + }, + }, + }, + }, + }, + }, + { + "name": "log_neighbor_changes", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \s(?P<log_neighbor_changes>log-neighbor-changes) + (\s(?P<disable>disable))? + $""", re.VERBOSE, + ), + "setval": "log-neighbor-changes{{ ' disable' if log_neighbor_changes.disable is defined }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "log_neighbor_changes": { + "set": "{{ True if log_neighbor_changes is defined and disable is undefined }}", + "disable": "{{ not not disable }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "low_memory", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \slow-memory\s(?P<exempt>exempt) + $""", re.VERBOSE, + ), + "setval": "low-memory exempt", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "low_memory": { + "exempt": "{{ not not exempt }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "password", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \spassword\s(?P<encryption>\d+) + \s(?P<key>\S+) + $""", re.VERBOSE, + ), + "setval": "password{{ (' ' + password.encryption|string) if password.encryption is defined }} {{ password.key }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "password": { + "encryption": "{{ encryption }}", + "key": "{{ key }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "path_attribute", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \spath-attribute\s(?P<action>\S+)\s + (?P<type>\d+)? + (range\s(?P<start>\d+)\s(?P<end>\d+))? + \sin + $""", re.VERBOSE, + ), + "setval": _tmplt_path_attribute, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "path_attribute": [ + { + "action": "{{ action }}", + "type": "{{ type if type is defined else None }}", + "range": { + "start": "{{ start if start is defined }}", + "end": "{{ end if end is defined }}", + }, + }, + ], + }, + }, + }, + }, + }, + }, + { + "name": "peer_type", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \speer-type\s(?P<peer_type>\S+) + $""", re.VERBOSE, + ), + "setval": "peer-type {{ peer_type }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "peer_type": "{{ peer_type }}", + }, + }, + }, + }, + }, + }, + { + "name": "remove_private_as", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \s(?P<remove_private_as>remove-private-as) + (\s(?P<all>all))? + (\s(?P<replace_as>replace-as))? + $""", re.VERBOSE, + ), + "setval": "remove-private-as", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "remove_private_as": { + "set": "{{ True if remove_private_as is defined and replace_as is undefined and all is undefined else None }}", + "replace_as": "{{ not not replace_as }}", + "all": "{{ not not all }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "shutdown", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \s(?P<shutdown>shutdown) + $""", re.VERBOSE, + ), + "setval": "shutdown", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "shutdown": "{{ not not shutdown }}", + }, + }, + }, + }, + }, + }, + { + "name": "timers", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \stimers\s(?P<keepalive>\d+)\s(?P<holdtime>\d+) + $""", re.VERBOSE, + ), + "setval": "timers {{ timers.keepalive }} {{ timers.holdtime }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "timers": { + "keepalive": "{{ keepalive }}", + "holdtime": "{{ holdtime }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "transport", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \stransport\sconnection-mode + \s(?P<passive>passive) + $""", re.VERBOSE, + ), + "setval": "transport connection-mode passive", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "transport": { + "connection_mode": { + "passive": "{{ not not passive }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "ttl_security", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \sttl-security\shops\s(?P<hops>\d+) + $""", re.VERBOSE, + ), + "setval": "ttl-security hops {{ ttl_security.hops }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "ttl_security": { + "hops": "{{ hops }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "update_source", + "getval": re.compile( + r""" + \s+neighbor\s(?P<neighbor_address>\S+) + \supdate-source\s(?P<update_source>\S+) + $""", re.VERBOSE, + ), + "setval": "update-source {{ update_source }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbors": { + "{{ neighbor_address }}": { + "update_source": "{{ update_source }}", + }, + }, + }, + }, + }, + }, + # end neighbor parsers + { + "name": "neighbor_down.fib_accelerate", + "getval": re.compile( + r""" + \s+neighbor-down\s(?P<fib_accelerate>fib-accelerate) + $""", re.VERBOSE, + ), + "setval": "neighbor-down fib-accelerate", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "neighbor_down": { + "fib_accelerate": "{{ not not fib_accelerate }}", + }, + }, + }, + }, + }, + { + "name": "nexthop.suppress_default_resolution", + "getval": re.compile( + r""" + \s+nexthop + \s(?P<suppress_default_resolution>suppress-default-resolution) + $""", re.VERBOSE, + ), + "setval": "nexthop suppress-default-resolution", + "result": { + "nexthop": { + "suppress_default_resolution": "{{ not not suppress_default_resolution }}", + }, + }, + }, + { + "name": "reconnect_interval", + "getval": re.compile( + r""" + \s+reconnect-interval + \s(?P<reconnect_interval>\d+) + $""", re.VERBOSE, + ), + "setval": "reconnect-interval {{ reconnect_interval }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "reconnect_interval": "{{ reconnect_interval }}", + }, + }, + }, + }, + { + "name": "router_id", + "getval": re.compile( + r""" + \s+router-id + \s(?P<router_id>\S+) + $""", re.VERBOSE, + ), + "setval": "router-id {{ router_id }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "router_id": "{{ router_id }}", + }, + }, + }, + }, + { + "name": "shutdown", + "getval": re.compile( + r""" + \s+(?P<shutdown>shutdown) + $""", re.VERBOSE, + ), + "setval": "shutdown", + "result": { + "shutdown": "{{ not not shutdown }}", + }, + }, + { + "name": "suppress_fib_pending", + "getval": re.compile( + r""" + \s+no\s(?P<suppress_fib_pending>suppress-fib-pending) + $""", re.VERBOSE, + ), + "setval": "suppress-fib-pending", + "result": { + "suppress_fib_pending": "{{ not suppress_fib_pending }}", + }, + }, + { + "name": "timers.bestpath_limit", + "getval": re.compile( + r""" + \s+timers\sbestpath-limit + \s(?P<timeout>\d+) + (\s(?P<always>always))? + $""", re.VERBOSE, + ), + "setval": "timers bestpath-limit {{ timers.bestpath_limit.timeout }}{{ ' always' if timers.bestpath_limit.timeout is defined }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "bestpath_limit": { + "timeout": "{{ timeout }}", + "always": "{{ not not always }}", + }, + }, + }, + }, + }, + }, + { + "name": "timers.bgp", + "getval": re.compile( + r""" + \s+timers\sbgp + \s(?P<keepalive>\d+) + (\s(?P<holdtime>\d+))? + $""", re.VERBOSE, + ), + "setval": "timers bgp {{ timers.bgp.keepalive }} {{ timers.bgp.holdtime }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "bgp": { + "keepalive": "{{ keepalive }}", + "holdtime": "{{ holdtime }}", + }, + }, + }, + }, + }, + }, + { + "name": "timers.prefix_peer_timeout", + "getval": re.compile( + r""" + \s+timers + \sprefix-peer-timeout\s(?P<prefix_peer_timeout>\d+) + $""", re.VERBOSE, + ), + "setval": "timers prefix-peer-timeout {{ timers.prefix_peer_timeout }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "prefix_peer_timeout": "{{ prefix_peer_timeout }}", + }, + }, + }, + }, + }, + { + "name": "timers.prefix_peer_wait", + "getval": re.compile( + r""" + \s+timers + \sprefix-peer-wait\s(?P<prefix_peer_wait>\d+) + $""", re.VERBOSE, + ), + "setval": "timers prefix-peer-wait {{ timers.prefix_peer_wait }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "prefix_peer_wait": "{{ prefix_peer_wait }}", + }, + }, + }, + }, + }, + { + "name": "fabric_soo", + "getval": re.compile( + r""" + \s+fabric-soo + \s(?P<fabric_soo>\S+) + $""", re.VERBOSE, + ), + "setval": "fabric-soo {{ fabric_soo }}", + "result": { + "fabric_soo": "{{ fabric_soo }}", + }, + }, + { + "name": "rd", + "getval": re.compile( + r""" + \s+rd\s(?P<dual>dual) + (\sid\s(?P<id>\d+))? + $""", re.VERBOSE, + ), + "setval": "rd dual{{' id ' + rd.id if rd.id is defined }}", + "result": { + "rd": { + "dual": "{{ not not dual }}", + "id": "{{ id }}", + }, + }, + }, + # VRF only + { + "name": "allocate_index", + "getval": re.compile( + r""" + \s+allocate-index\s(?P<allocate_index>\d+) + $""", re.VERBOSE, + ), + "setval": "allocate-index {{ allocate_index }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "allocate_index": "{{ allocate_index }}", + }, + }, + }, + }, + # VRF only + { + "name": "local_as", + "getval": re.compile( + r""" + \s+local-as\s(?P<local_as>\d+) + $""", re.VERBOSE, + ), + "setval": "local-as {{ local_as }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "local_as": "{{ local_as }}", + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_neighbor_address_family.py new file mode 100644 index 00000000..327ba4d2 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_neighbor_address_family.py @@ -0,0 +1,894 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Bgp_neighbor_address_family parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_maximum_prefix(data): + data = data["maximum_prefix"] + cmd = "maximum-prefix {max_prefix_limit}".format(**data) + if "generate_warning_threshold" in data: + cmd += " {generate_warning_threshold}".format(**data) + if "restart_interval" in data: + cmd += " restart {restart_interval}".format(**data) + if data.get("warning_only"): + cmd += " warning-only" + return cmd + + +class Bgp_neighbor_address_familyTemplate(NetworkTemplate): + def __init__(self, lines=None): + super(Bgp_neighbor_address_familyTemplate, self).__init__(lines=lines, tmplt=self) + + # fmt: off + PARSERS = [ + { + "name": "as_number", + "getval": re.compile( + r""" + ^router\sbgp\s(?P<as_number>\S+) + $""", re.VERBOSE, + ), + "setval": "router bgp {{ as_number }}", + "result": { + "as_number": "{{ as_number }}", + }, + "shared": True, + }, + { + "name": "address_family", + "getval": re.compile( + r""" + (vrf\s(?P<vrf>\S+))? + \s*neighbor\s(?P<neighbor>\S+) + \saddress-family + \s(?P<afi>\S+) + (\s(?P<safi>\S+))? + $""", + re.VERBOSE, + ), + "setval": "address-family {{ afi }}{{ (' ' + safi) if safi is defined else '' }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "neighbor_address": "{{ neighbor }}", + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + }, + }, + }, + }, + }, + }, + }, + "shared": True, + }, + { + "name": "advertise_map.exist_map", + "getval": re.compile( + r""" + advertise-map + \s(?P<route_map>\S+) + \sexist-map\s(?P<exist_map>\S+) + $""", + re.VERBOSE, + ), + "setval": "advertise-map {{ advertise_map.route_map }} exist-map {{ advertise_map.exist_map }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "advertise_map": { + "route_map": "{{ route_map }}", + "exist_map": "{{ exist_map }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "advertise_map.non_exist_map", + "getval": re.compile( + r""" + advertise-map + \s(?P<route_map>\S+) + \snon-exist-map\s(?P<non_exist_map>\S+) + $""", + re.VERBOSE, + ), + "setval": "advertise-map {{ advertise_map.route_map }} non-exist-map {{ advertise_map.non_exist_map }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "advertise_map": { + "route_map": "{{ route_map }}", + "non_exist_map": "{{ non_exist_map }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "advertisement_interval", + "getval": re.compile( + r""" + advertisement-interval + \s(?P<advertisement_interval>\d+) + $""", + re.VERBOSE, + ), + "setval": "advertisement-interval {{ advertisement_interval }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "advertisement_interval": "{{ advertisement_interval }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "allowas_in", + "getval": re.compile( + r""" + (?P<allowas_in>allowas-in) + \s(?P<max_occurences>\d+) + $""", + re.VERBOSE, + ), + "setval": "allowas-in{{ ' ' + allowas_in.max_occurences|string if allowas_in.max_occurences is defined else '' }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "allowas_in": { + "set": "{{ True if allowas_in is defined and max_occurences is undefined }}", + "max_occurences": "{{ max_occurences }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "as_override", + "getval": re.compile( + r""" + (?P<as_override>as-override) + $""", + re.VERBOSE, + ), + "setval": "as-override", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "as_override": "{{ not not as_override }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "capability.additional_paths.receive", + "getval": re.compile( + r""" + capability\sadditional-paths + \s(?P<receive>receive) + (\s(?P<disable>disable))? + $""", + re.VERBOSE, + ), + "setval": "capability additional-paths receive{{ ' disable' if capability.additional_paths.receive == 'disable' else '' }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "capability": { + "additional_paths": { + "receive": "{{ 'disable' if disable is defined else 'enable' }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "capability.additional_paths.send", + "getval": re.compile( + r""" + capability\sadditional-paths + \s(?P<send>send) + (\s(?P<disable>disable))? + $""", + re.VERBOSE, + ), + "setval": "capability additional-paths send{{ ' disable' if capability.additional_paths.send == 'disable' else '' }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "capability": { + "additional_paths": { + "send": "{{ 'disable' if disable is defined else 'enable' }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "default_originate", + "getval": re.compile( + r""" + (?P<default_originate>default-originate) + (\sroute-map\s(?P<route_map>\S+))? + $""", + re.VERBOSE, + ), + "setval": "default-originate{{ ' route-map ' + default_originate.route_map if default_originate.route_map is defined else '' }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "default_originate": { + "set": "{{ True if default_originate is defined and route_map is not defined }}", + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "disable_peer_as_check", + "getval": re.compile( + r""" + (?P<disable_peer_as_check>disable-peer-as-check) + $""", + re.VERBOSE, + ), + "setval": "disable-peer-as-check", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "disable_peer_as_check": "{{ not not disable_peer_as_check }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "filter_list.inbound", + "getval": re.compile( + r""" + filter-list + \s(?P<in>\S+)\s(?:in) + $""", + re.VERBOSE, + ), + "setval": "filter-list {{ filter_list.inbound }} in", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "filter_list": { + "inbound": "{{ in }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "filter_list.outbound", + "getval": re.compile( + r""" + filter-list + \s(?P<out>\S+)\s(?:out) + $""", + re.VERBOSE, + ), + "setval": "filter-list {{ filter_list.outbound }} out", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "filter_list": { + "outbound": "{{ out }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "inherit", + "getval": re.compile( + r""" + inherit\speer-policy + \s(?P<template>\S+) + \s(?P<sequence>\d+) + $""", + re.VERBOSE, + ), + "setval": "inherit peer-policy {{ inherit.template }} {{ inherit.sequence }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "inherit": { + "template": "{{ template }}", + "sequence": "{{ sequence }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "maximum_prefix", + "getval": re.compile( + r""" + maximum-prefix + \s(?P<max_prefix_limit>\d+) + (\s(?P<generate_warning_threshold>\d+))? + (\srestart\s(?P<restart_interval>\d+))? + (\s(?P<warning_only>warning-only))? + $""", + re.VERBOSE, + ), + "setval": _tmplt_maximum_prefix, + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "maximum_prefix": { + "max_prefix_limit": "{{ max_prefix_limit }}", + "generate_warning_threshold": "{{ generate_warning_threshold }}", + "restart_interval": "{{ restart_interval }}", + "warning_only": "{{ not not warning_only }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "next_hop_self", + "getval": re.compile( + r""" + (?P<next_hop_self>next-hop-self) + (\s(?P<all_routes>all))? + $""", + re.VERBOSE, + ), + "setval": "next-hop-self{{ ' all' if next_hop_self.all_routes|d(False) else '' }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "next_hop_self": { + "set": "{{ True if next_hop_self is defined and all_routes is not defined }}", + "all_routes": "{{ not not all_routes }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "next_hop_third_party", + "getval": re.compile( + r""" + no\s(?P<next_hop_third_party>next-hop-third-party) + $""", + re.VERBOSE, + ), + "setval": "next-hop-third-party", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "next_hop_third_party": "{{ not next_hop_third_party }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "prefix_list.inbound", + "getval": re.compile( + r""" + prefix-list + \s(?P<in>\S+)\s(?:in) + $""", + re.VERBOSE, + ), + "setval": "prefix-list {{ prefix_list.inbound }} in", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "prefix_list": { + "inbound": "{{ in }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "prefix_list.outbound", + "getval": re.compile( + r""" + prefix-list + \s(?P<out>\S+)\s(?:out) + $""", + re.VERBOSE, + ), + "setval": "prefix-list {{ prefix_list.outbound }} out", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "prefix_list": { + "outbound": "{{ out }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "rewrite_evpn_rt_asn", + "getval": re.compile( + r""" + (?P<rewrite_evpn_rt_asn>rewrite-evpn-rt-asn) + $""", + re.VERBOSE, + ), + "setval": "rewrite-evpn-rt-asn", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "rewrite_evpn_rt_asn": "{{ not not rewrite_evpn_rt_asn }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "route_map.inbound", + "getval": re.compile( + r""" + route-map + \s(?P<in>\S+)\s(?:in) + $""", + re.VERBOSE, + ), + "setval": "route-map {{ route_map.inbound }} in", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "route_map": { + "inbound": "{{ in }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "route_map.outbound", + "getval": re.compile( + r""" + route-map + \s(?P<out>\S+)\s(?:out) + $""", + re.VERBOSE, + ), + "setval": "route-map {{ route_map.outbound }} out", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "route_map": { + "outbound": "{{ out }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "route_reflector_client", + "getval": re.compile( + r""" + (?P<route_reflector_client>route-reflector-client) + $""", + re.VERBOSE, + ), + "setval": "route-reflector-client", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "route_reflector_client": "{{ not not route_reflector_client }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "send_community.standard", + "getval": re.compile( + r""" + (?P<send_community>send-community) + $""", + re.VERBOSE, + ), + "setval": "send-community", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "send_community": { + "standard": "{{ True if send_community is defined }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "send_community.extended", + "getval": re.compile( + r""" + send-community + \s(?P<extended>extended) + $""", + re.VERBOSE, + ), + "setval": "send-community extended", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "send_community": { + "extended": "{{ True if extended is defined }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "soft_reconfiguration_inbound", + "getval": re.compile( + r""" + (?P<soft_reconfiguration_inbound>soft-reconfiguration\sinbound) + (\s(?P<always>always))? + $""", + re.VERBOSE, + ), + "setval": "soft-reconfiguration inbound{{ ' always' if soft_reconfiguration_inbound.always|d(False) else '' }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "soft_reconfiguration_inbound": { + "set": "{{ True if soft_reconfiguration_inbound is defined and always is undefined }}", + "always": "{{ not not always }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "soo", + "getval": re.compile( + r""" + soo\s(?P<soo>\S+) + $""", + re.VERBOSE, + ), + "setval": "soo {{ soo }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "soo": "{{ soo }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "suppress_inactive", + "getval": re.compile( + r""" + (?P<suppress_inactive>suppress-inactive) + $""", + re.VERBOSE, + ), + "setval": "suppress-inactive", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "suppress_inactive": "{{ not not suppress_inactive }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "unsuppress_map", + "getval": re.compile( + r""" + unsuppress-map\s(?P<unsuppress_map>\S+) + $""", + re.VERBOSE, + ), + "setval": "unsuppress-map {{ unsuppress_map }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "unsuppress_map": "{{ unsuppress_map }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "weight", + "getval": re.compile( + r""" + weight\s(?P<weight>\d+) + $""", + re.VERBOSE, + ), + "setval": "weight {{ weight }}", + "result": { + "vrfs": { + "{{ 'vrf_' + vrf|d() }}": { + "vrf": "{{ vrf }}", + "neighbors": { + "{{ neighbor }}": { + "address_family": { + '{{ afi + "_" + safi|d() }}': { + "weight": "{{ weight }}", + }, + }, + }, + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/hostname.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/hostname.py new file mode 100644 index 00000000..bf922fb4 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/hostname.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# 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 + +""" +The Hostname parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +class HostnameTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(HostnameTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "hostname", + "getval": re.compile( + r""" + ^hostname\s(?P<hostname>\S+) + $""", re.VERBOSE, + ), + "setval": "hostname {{ hostname }}", + "result": { + "hostname": "{{ hostname }}", + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/logging_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/logging_global.py new file mode 100644 index 00000000..8287794e --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/logging_global.py @@ -0,0 +1,480 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Logging_global parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_hosts(data): + cmd = "logging server {host}" + data["client_identity"] = data.get("secure", {}).get("trustpoint", {}).get("client_identity") + + if "severity" in data: + cmd += " {severity}" + if "port" in data: + cmd += " port {port}" + if data["client_identity"]: + cmd += " secure trustpoint client-identity {client_identity}" + if "facility" in data: + cmd += " facility {facility}" + if "use_vrf" in data: + cmd += " use-vrf {use_vrf}" + + cmd = cmd.format(**data) + + return cmd + + +class Logging_globalTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Logging_globalTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "console", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging\sconsole + (\s(?P<severity>\d))? + $""", re.VERBOSE, + ), + "setval": "{{ 'no ' if console.state|d('') == 'disabled' else '' }}" + "logging console" + "{{ (' ' + console.severity|string) if console.severity is defined else '' }}", + "result": { + "console": { + "state": "{{ 'disabled' if negated is defined else None }}", + "severity": "{{ severity }}", + }, + }, + }, + { + "name": "event.link_status.enable", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging\sevent\slink-status\senable + $""", re.VERBOSE, + ), + "setval": "logging event link-status enable", + "result": { + "event": { + "link_status": { + "enable": "{{ False if negated is defined else True }}", + }, + }, + }, + }, + { + "name": "event.link_status.default", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging\sevent\slink-status\sdefault + $""", re.VERBOSE, + ), + "setval": "logging event link-status default", + "result": { + "event": { + "link_status": { + "default": "{{ False if negated is defined else True }}", + }, + }, + }, + }, + { + "name": "event.trunk_status.enable", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging\sevent\strunk-status\senable + $""", re.VERBOSE, + ), + "setval": "logging event trunk-status enable", + "result": { + "event": { + "trunk_status": { + "enable": "{{ False if negated is defined else True }}", + }, + }, + }, + }, + { + "name": "event.trunk_status.default", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging\sevent\strunk-status\sdefault + $""", re.VERBOSE, + ), + "setval": "logging event trunk-status default", + "result": { + "event": { + "trunk_status": { + "default": "{{ False if negated is defined else True }}", + }, + }, + }, + }, + { + "name": "history.severity", + "getval": re.compile( + r""" + ^logging\shistory + \s(?P<severity>\d) + $""", re.VERBOSE, + ), + "setval": "logging history {{ history.severity }}", + "result": { + "history": { + "severity": "{{ severity }}", + }, + }, + }, + { + "name": "history.size", + "getval": re.compile( + r""" + ^logging\shistory\ssize + \s(?P<size>\d+) + $""", re.VERBOSE, + ), + "setval": "logging history size {{ history.size }}", + "result": { + "history": { + "size": "{{ size }}", + }, + }, + }, + { + "name": "ip.access_list.cache.entries", + "getval": re.compile( + r""" + ^logging\sip\saccess-list\scache + \sentries\s(?P<entries>\d+) + $""", re.VERBOSE, + ), + "setval": "logging ip access-list cache entries {{ ip.access_list.cache.entries }}", + "result": { + "ip": { + "access_list": { + "cache": { + "entries": "{{ entries }}", + }, + }, + }, + }, + }, + { + "name": "ip.access_list.cache.interval", + "getval": re.compile( + r""" + ^logging\sip\saccess-list\scache + \sinterval\s(?P<interval>\d+) + $""", re.VERBOSE, + ), + "setval": "logging ip access-list cache interval {{ ip.access_list.cache.interval }}", + "result": { + "ip": { + "access_list": { + "cache": { + "interval": "{{ interval }}", + }, + }, + }, + }, + }, + { + "name": "ip.access_list.cache.threshold", + "getval": re.compile( + r""" + ^logging\sip\saccess-list\scache + \sthreshold\s(?P<threshold>\d+) + $""", re.VERBOSE, + ), + "setval": "logging ip access-list cache threshold {{ ip.access_list.cache.threshold }}", + "result": { + "ip": { + "access_list": { + "cache": { + "threshold": "{{ threshold }}", + }, + }, + }, + }, + }, + { + "name": "ip.access_list.detailed", + "getval": re.compile( + r""" + ^logging\sip\saccess-list + \s(?P<detailed>detailed) + $""", re.VERBOSE, + ), + "setval": "logging ip access-list detailed", + "result": { + "ip": { + "access_list": { + "detailed": "{{ not not detailed }}", + }, + }, + }, + }, + { + "name": "ip.access_list.include.sgt", + "getval": re.compile( + r""" + ^logging\sip\saccess-list\sinclude + \s(?P<sgt>sgt) + $""", re.VERBOSE, + ), + "setval": "logging ip access-list include sgt", + "result": { + "ip": { + "access_list": { + "include": { + "sgt": "{{ not not sgt }}", + }, + }, + }, + }, + }, + { + # in some cases, the `logging level` command + # has an extra space at the end + "name": "facilities", + "getval": re.compile( + r""" + ^logging\slevel + \s(?P<facility>\S+) + \s(?P<severity>\d+) + \s* + $""", re.VERBOSE, + ), + "setval": "logging level {{ facility }} {{ severity }}", + "result": { + "facilities": [ + { + "facility": "{{ facility }}", + "severity": "{{ severity }}", + }, + ], + }, + }, + { + "name": "logfile", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging\slogfile + (\s(?P<name>\S+))? + (\s(?P<severity>\d+))? + (\ssize\s(?P<size>\d+))? + (\spersistent\sthreshold\s(?P<persistent_threshold>\d+))? + $""", re.VERBOSE, + ), + "setval": "{{ 'no ' if logfile.state|d('') == 'disabled' else '' }}" + "logging logfile" + "{{ ' ' + logfile.name if logfile.name|d('') else '' }}" + "{{ (' ' + logfile.severity|string) if logfile.severity is defined else '' }}" + "{{ (' size ' + logfile.size|string) if logfile.size is defined else '' }}" + "{{ (' persistent threshold ' + logfile.persistent_threshold|string) if logfile.persistent_threshold is defined else '' }}", + "result": { + "logfile": { + "state": "{{ 'disabled' if negated is defined else None }}", + "name": "{{ name }}", + "severity": "{{ severity }}", + "persistent_threshold": "{{ persistent_threshold }}", + "size": "{{ size }}", + }, + }, + }, + { + "name": "module", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging\smodule + (\s(?P<severity>\d))? + $""", re.VERBOSE, + ), + "setval": "{{ 'no ' if module.state|d('') == 'disabled' else '' }}" + "logging module" + "{{ (' ' + module.severity|string) if module.severity is defined else '' }}", + "result": { + "module": { + "state": "{{ 'disabled' if negated is defined else None }}", + "severity": "{{ severity }}", + }, + }, + }, + { + "name": "monitor", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging\smonitor + (\s(?P<severity>\d))? + $""", re.VERBOSE, + ), + "setval": "{{ 'no ' if monitor.state|d('') == 'disabled' else '' }}" + "logging monitor" + "{{ (' ' + monitor.severity|string) if monitor.severity is defined else '' }}", + "result": { + "monitor": { + "state": "{{ 'disabled' if negated is defined else None }}", + "severity": "{{ severity }}", + }, + }, + }, + { + "name": "origin_id.hostname", + "getval": re.compile( + r""" + ^logging\sorigin-id + \s(?P<hostname>hostname) + $""", re.VERBOSE, + ), + "setval": "logging origin-id hostname", + "result": { + "origin_id": { + "hostname": "{{ not not hostname }}", + }, + }, + }, + { + "name": "origin_id.ip", + "getval": re.compile( + r""" + ^logging\sorigin-id + \sip\s(?P<ip>\S+) + $""", re.VERBOSE, + ), + "setval": "logging origin-id ip {{ origin_id.ip }}", + "result": { + "origin_id": { + "ip": "{{ ip }}", + }, + }, + }, + { + "name": "origin_id.string", + "getval": re.compile( + r""" + ^logging\sorigin-id + \sstring\s(?P<string>\S+) + $""", re.VERBOSE, + ), + "setval": "logging origin-id string {{ origin_id.string }}", + "result": { + "origin_id": { + "string": "{{ string }}", + }, + }, + }, + { + "name": "rate_limit", + "getval": re.compile( + r""" + ^(?P<negated>no\s)? + logging + \s(?P<rate_limit>rate-limit) + $""", re.VERBOSE, + ), + "setval": "{{ 'no ' if rate_limit|d('') == 'disabled' else '' }}" + "logging rate-limit", + "result": { + "rate_limit": "{{ 'disabled' if negated is defined else None }}", + }, + }, + { + "name": "rfc_strict", + "getval": re.compile( + r""" + logging\srfc-strict + \s(?P<rfc_strict>5424) + $""", re.VERBOSE, + ), + "setval": "logging rfc-strict 5424", + "result": { + "rfc_strict": "{{ not not rfc_strict }}", + }, + }, + { + "name": "hosts", + "getval": re.compile( + r""" + ^logging\sserver + \s(?P<host>\S+) + (\s(?P<severity>\d))? + (\sport\s(?P<port>\d+))? + (\ssecure\strustpoint\sclient-identity\s(?P<client_identity>\S+))? + (\suse-vrf\s(?P<use_vrf>\S+))? + (\sfacility\s(?P<facility>\S+))? + $""", re.VERBOSE, + ), + "setval": _tmplt_hosts, + "result": { + "hosts": [ + { + "host": "{{ host }}", + "severity": "{{ severity }}", + "secure": { + "trustpoint": { + "client_identity": "{{ client_identity }}", + }, + }, + "port": "{{ port }}", + "facility": "{{ facility }}", + "use_vrf": "{{ use_vrf }}", + }, + ], + }, + }, + { + "name": "source_interface", + "getval": re.compile( + r""" + ^logging\ssource-interface + \s(?P<source_interface>\S+) + $""", re.VERBOSE, + ), + "setval": "logging source-interface {{ source_interface }}", + "result": { + "source_interface": "{{ source_interface }}", + }, + }, + { + "name": "timestamp", + "getval": re.compile( + r""" + ^logging\stimestamp + \s(?P<timestamp>\S+) + $""", re.VERBOSE, + ), + "setval": "logging timestamp {{ timestamp }}", + "result": { + "timestamp": "{{ timestamp }}", + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ntp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ntp_global.py new file mode 100644 index 00000000..8d5a354d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ntp_global.py @@ -0,0 +1,320 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Ntp_global parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +class Ntp_globalTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Ntp_globalTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "access_group.match_all", + "getval": re.compile( + r""" + ^ntp\saccess-group\s(?P<match_all>match-all) + $""", re.VERBOSE, + ), + "setval": "ntp access-group match-all", + "result": { + "access_group": { + "match_all": "{{ True if match_all is defined else None }}", + }, + }, + }, + { + "name": "peer", + "getval": re.compile( + r""" + ^ntp\saccess-group\speer\s(?P<acl>\S+) + $""", re.VERBOSE, + ), + "setval": "ntp access-group peer {{ access_list }}", + "result": { + "access_group": { + "peer": [ + { + "access_list": "{{ acl }}", + }, + ], + }, + }, + }, + { + "name": "query_only", + "getval": re.compile( + r""" + ^ntp\saccess-group\squery-only\s(?P<acl>\S+) + $""", re.VERBOSE, + ), + "setval": "ntp access-group query-only {{ access_list }}", + "result": { + "access_group": { + "query_only": [ + { + "access_list": "{{ acl }}", + }, + ], + }, + }, + }, + { + "name": "serve", + "getval": re.compile( + r""" + ^ntp\saccess-group\sserve\s(?P<acl>\S+) + $""", re.VERBOSE, + ), + "setval": "ntp access-group serve {{ access_list }}", + "result": { + "access_group": { + "serve": [ + { + "access_list": "{{ acl }}", + }, + ], + }, + }, + }, + { + "name": "serve_only", + "getval": re.compile( + r""" + ^ntp\saccess-group\sserve-only\s(?P<acl>\S+) + $""", re.VERBOSE, + ), + "setval": "ntp access-group serve-only {{ access_list }}", + "result": { + "access_group": { + "serve_only": [ + { + "access_list": "{{ acl }}", + }, + ], + }, + }, + }, + { + "name": "allow.control.rate_limit", + "getval": re.compile( + r""" + ^ntp\sallow\scontrol\srate-limit\s(?P<rate_limit>\d+) + $""", re.VERBOSE, + ), + "setval": "ntp allow control rate-limit {{ allow.control.rate_limit }}", + "result": { + "allow": { + "control": { + "rate_limit": "{{ rate_limit }}", + }, + }, + }, + }, + { + "name": "allow.private", + "getval": re.compile( + r""" + ^ntp\sallow\s(?P<private>private) + $""", re.VERBOSE, + ), + "setval": "ntp allow private", + "result": { + "allow": { + "private": "{{ not not private }}", + }, + }, + }, + { + "name": "authenticate", + "getval": re.compile( + r""" + ^ntp\s(?P<authenticate>authenticate) + $""", re.VERBOSE, + ), + "setval": "ntp authenticate", + "result": { + "authenticate": "{{ not not authenticate }}", + }, + }, + { + "name": "authentication_keys", + "getval": re.compile( + r""" + ^ntp\sauthentication-key\s(?P<id>\d+)\smd5\s(?P<key>\S+)\s(?P<encryption>\d+) + $""", re.VERBOSE, + ), + "setval": "ntp authentication-key {{ id }} md5 {{ key }} {{ encryption }}", + "result": { + "authentication_keys": [ + { + "id": "{{ id }}", + "key": "{{ key }}", + "encryption": "{{ encryption }}", + }, + ], + }, + }, + { + "name": "logging", + "getval": re.compile( + r""" + ^ntp\s(?P<logging>logging) + $""", re.VERBOSE, + ), + "setval": "ntp logging", + "result": { + "logging": "{{ not not logging }}", + }, + }, + { + "name": "master.stratum", + "getval": re.compile( + r""" + ^ntp\smaster\s(?P<stratum>\d+) + $""", re.VERBOSE, + ), + "setval": "ntp master {{ master.stratum }}", + "result": { + "master": { + "stratum": "{{ stratum }}", + }, + }, + }, + { + "name": "passive", + "getval": re.compile( + r""" + ^ntp\s(?P<passive>passive) + $""", re.VERBOSE, + ), + "setval": "ntp passive", + "result": { + "passive": "{{ not not passive }}", + }, + }, + { + "name": "peers", + "getval": re.compile( + r""" + ^ntp\speer + \s(?P<peer>\S+) + (\s(?P<prefer>prefer))? + (\suse-vrf\s(?P<use_vrf>\S+))? + (\skey\s(?P<key>\d+))? + (\sminpoll\s(?P<minpoll>\d+))? + (\smaxpoll\s(?P<maxpoll>\d+))? + $""", re.VERBOSE, + ), + "setval": "ntp peer {{ peer }}" + "{{ ' prefer' if prefer is defined else ''}}" + "{{ (' use-vrf ' + vrf) if vrf is defined else '' }}" + "{{ (' key ' + key_id|string) if key_id is defined else '' }}" + "{{ (' minpoll ' + minpoll|string) if minpoll is defined else '' }}" + "{{ (' maxpoll ' + maxpoll|string) if maxpoll is defined else '' }}", + "result": { + "peers": [ + { + "peer": "{{ peer }}", + "prefer": "{{ not not prefer }}", + "vrf": "{{ use_vrf }}", + "key_id": "{{ key }}", + "minpoll": "{{ minpoll }}", + "maxpoll": "{{ maxpoll }}", + }, + ], + }, + }, + { + "name": "servers", + "getval": re.compile( + r""" + ^ntp\sserver + \s(?P<server>\S+) + (\s(?P<prefer>prefer))? + (\suse-vrf\s(?P<use_vrf>\S+))? + (\skey\s(?P<key>\d+))? + (\sminpoll\s(?P<minpoll>\d+))? + (\smaxpoll\s(?P<maxpoll>\d+))? + $""", re.VERBOSE, + ), + "setval": "ntp server {{ server }}" + "{{ ' prefer' if prefer is defined else ''}}" + "{{ (' use-vrf ' + vrf) if vrf is defined else '' }}" + "{{ (' key ' + key_id|string) if key_id is defined else '' }}" + "{{ (' minpoll ' + minpoll|string) if minpoll is defined else '' }}" + "{{ (' maxpoll ' + maxpoll|string) if maxpoll is defined else '' }}", + "result": { + "servers": [ + { + "server": "{{ server }}", + "prefer": "{{ not not prefer }}", + "vrf": "{{ use_vrf }}", + "key_id": "{{ key }}", + "minpoll": "{{ minpoll }}", + "maxpoll": "{{ maxpoll }}", + }, + ], + }, + }, + { + "name": "source", + "getval": re.compile( + r""" + ^ntp\ssource\s(?P<source>\S+) + $""", re.VERBOSE, + ), + "setval": "ntp source {{ source }}", + "result": { + "source": "{{ source }}", + }, + }, + { + "name": "source_interface", + "getval": re.compile( + r""" + ^ntp\ssource-interface(\s)+(?P<source_interface>\S+) + $""", re.VERBOSE, + ), + "setval": "ntp source-interface {{ source_interface }}", + "result": { + "source_interface": "{{ source_interface }}", + }, + }, + { + "name": "trusted_keys", + "getval": re.compile( + r""" + ^ntp\strusted-key\s(?P<key>\d+) + $""", re.VERBOSE, + ), + "setval": "ntp trusted-key {{ key_id|string }}", + "result": { + "trusted_keys": [ + { + "key_id": "{{ key }}", + }, + ], + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospf_interfaces.py new file mode 100644 index 00000000..2321fcaf --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospf_interfaces.py @@ -0,0 +1,510 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# 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 + +""" +The Ospf_interfaces parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_authentication(data): + auth = data.get("authentication") + cmd = "ip ospf authentication" + + if auth.get("enable") is False: + cmd = "no " + cmd + else: + if auth.get("message_digest"): + cmd += " message-digest" + elif auth.get("null_auth"): + cmd += " null" + return cmd + + +class Ospf_interfacesTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Ospf_interfacesTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "interface", + "getval": re.compile( + r''' + ^interface + \s(?P<name>\S+)$''', re.VERBOSE, + ), + "setval": "interface {{ name }}", + "result": { + "{{ name }}": { + "name": "{{ name }}", + "address_family": {}, + }, + }, + "shared": True, + }, + { + "name": "area", + "getval": re.compile( + r""" + \s+(?P<afi>ip|ipv6) + \srouter\s(ospf|ospfv3) + \s(?P<process_id>\S+) + \sarea\s(?P<area_id>\S+) + (\s(?P<secondaries>secondaries\snone))?$""", + re.VERBOSE, + ), + "setval": "{{ 'ip' if afi == 'ipv4' else 'ipv6' }} " + "router {{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "{{ process_id }} area {{ area.area_id }}{{ ' secondaries none' if area.secondaries|default('True') == False else '' }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi }}": { + "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}", + "processes": { + "{{ process_id }}": { + "process_id": "{{ process_id }}", + "area": { + "area_id": "{{ area_id }}", + "secondaries": "{{ False if secondaries is defined else None }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "processes_multi_areas", + "getval": re.compile( + r""" + \s+(?P<afi>ip|ipv6) + \srouter\s(ospf|ospfv3) + \s(?P<process_id>\S+) + \smulti-area\s(?P<area>\S+)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip' if afi == 'ipv4' else 'ipv6' }} " + "router {{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "{{ process_id }} multi-area {{ area }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi }}": { + "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}", + "processes": { + "{{ process_id }}": { + "process_id": "{{ process_id }}", + "multi_areas": [ + "{{ area }}", + ], + }, + }, + }, + }, + }, + }, + }, + { + "name": "multi_areas", + "getval": re.compile( + r""" + \s+(?P<afi>ip|ipv6) + \srouter\s(ospf|ospfv3) + \smulti-area\s(?P<area>\S+)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip' if afi == 'ipv4' else 'ipv6' }} " + "router {{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "multi-area {{ area }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi }}": { + "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}", + "multi_areas": [ + "{{ area }}", + ], + }, + }, + }, + }, + }, + { + "name": "authentication", + "getval": re.compile( + r""" + \s+(?P<afi>ip|ipv6) + \s(ospf|ospfv3) + \s(?P<authentication>authentication) + (\s(?P<opt>(message-digest|null)))?$""", + re.VERBOSE, + ), + "setval": _tmplt_authentication, + "result": { + "{{ name }}": { + "address_family": { + "{{ afi }}": { + "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}", + "authentication": { + "enable": "{{ True if authentication is defined and opt is undefined }}", + "message_digest": "{{ True if opt == 'message-digest' else None }}", + "null_auth": "{{ True if opt == 'null' else None }}", + }, + }, + }, + }, + }, + }, + { + "name": "authentication.key_chain", + "getval": re.compile( + r""" + \s+(?P<afi>ip) + \sospf + \s(?P<authentication>authentication) + \skey-chain\s(?P<key_chain>\S+)$""", + re.VERBOSE, + ), + "setval": "ip ospf authentication key-chain {{ authentication.key_chain }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi }}": { + "afi": "{{ afi|replace('ip', 'ipv4') }}", + "authentication": { + "key_chain": "{{ key_chain }}", + }, + }, + }, + }, + }, + }, + { + "name": "authentication_key", + "getval": re.compile( + r""" + \s+(?P<afi>ip) + \sospf + \sauthentication-key + \s(?P<encryption>\d) + \s(?P<key>\S+)$""", + re.VERBOSE, + ), + "setval": "ip ospf authentication-key " + "{{ authentication_key.encryption }} {{ authentication_key.key }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi }}": { + "afi": "{{ afi|replace('ip', 'ipv4') }}", + "authentication_key": { + "encryption": "{{ encryption }}", + "key": "{{ key }}", + }, + }, + }, + }, + }, + }, + { + "name": "message_digest_key", + "getval": re.compile( + r""" + \s+(?P<afi>ip) + \sospf + \smessage-digest-key + \s(?P<key_id>\d+) + \smd5 + \s(?P<encryption>\d) + \s(?P<key>\S+)$""", + re.VERBOSE, + ), + "setval": "ip ospf " + "message-digest-key {{ message_digest_key.key_id }} " + "md5 {{ message_digest_key.encryption|default('') }} {{ message_digest_key.key }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi }}": { + "afi": "{{ afi|replace('ip', 'ipv4') }}", + "message_digest_key": { + "key_id": "{{ key_id }}", + "encryption": "{{ encryption }}", + "key": "{{ key }}", + }, + }, + }, + }, + }, + }, + { + "name": "cost", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \scost\s(?P<cost>\d+)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "cost {{ cost }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "cost": "{{ cost }}", + }, + }, + }, + }, + }, + { + "name": "dead_interval", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \sdead-interval\s(?P<dead_interval>\d+)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "dead-interval {{ dead_interval }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "dead_interval": "{{ dead_interval }}", + }, + }, + }, + }, + }, + { + "name": "hello_interval", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \shello-interval\s(?P<hello_interval>\d+)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "hello-interval {{ hello_interval }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "hello_interval": "{{ hello_interval }}", + }, + }, + }, + }, + }, + { + "name": "instance", + "getval": re.compile( + r""" + \s+(ospf|ospfv3) + \sinstance\s(?P<instance>\d+)$""", + re.VERBOSE, + ), + "setval": "ospfv3 instance {{ instance }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "ipv6", + "instance": "{{ instance }}", + }, + }, + }, + }, + }, + { + "name": "mtu_ignore", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \s(?P<mtu_ignore>mtu-ignore)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "mtu-ignore", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "mtu_ignore": "{{ not not mtu_ignore }}", + }, + }, + }, + }, + }, + { + "name": "network", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \snetwork\s(?P<network>(broadcast|point-to-point))$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "network {{ network }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "network": "{{ network }}", + }, + }, + }, + }, + }, + { + "name": "passive_interface", + "getval": re.compile( + r""" + (\s+(?P<negated>no))? + (\s+(?P<afi>ip))? + \s*(ospf|ospfv3) + \s(?P<passive_interface>passive-interface)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "passive-interface", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "passive_interface": "{{ False if negated is defined else (not not passive_interface) }}", + }, + }, + }, + }, + }, + { + "name": "priority", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \spriority\s(?P<priority>\d+)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "priority {{ priority }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "priority": "{{ priority }}", + }, + }, + }, + }, + }, + { + "name": "retransmit_interval", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \sretransmit-interval\s(?P<retransmit_interval>\d+)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "retransmit-interval {{ retransmit_interval }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "retransmit_interval": "{{ retransmit_interval }}", + }, + }, + }, + }, + }, + { + "name": "shutdown", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \s(?P<shutdown>shutdown)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "shutdown", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "shutdown": "{{ not not shutdown }}", + }, + }, + }, + }, + }, + { + "name": "transmit_delay", + "getval": re.compile( + r""" + \s+(?P<afi>ip)? + \s(ospf|ospfv3) + \stransmit-delay\s(?P<transmit_delay>\d+)$""", + re.VERBOSE, + ), + "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}" + "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} " + "transmit-delay {{ transmit_delay }}", + "result": { + "{{ name }}": { + "address_family": { + "{{ afi|d('ipv6') }}": { + "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}", + "transmit_delay": "{{ transmit_delay }}", + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv2.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv2.py new file mode 100644 index 00000000..c8b518dd --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv2.py @@ -0,0 +1,1101 @@ +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_area_range(arange): + command = "area {area} range {range}".format(**arange) + if arange.get("not_advertise") is True: + command += " not-advertise" + if "cost" in arange: + command += " cost {cost}".format(**arange) + return command + + +def _tmplt_default_information(proc): + command = "default-information originate" + if "always" in proc["default_information"] and proc["default_information"]["always"]: + command += " always" + if "route_map" in proc["default_information"]: + command += " route-map" + command += " {default_information[route_map]}".format(**proc) + return command + + +def _tmplt_log_adjacency_changes(proc): + command = "log-adjacency-changes" + if proc.get("log_adjacency_changes").get("detail", False) is True: + command += " detail" + return command + + +def _tmplt_area_authentication(area): + command = "area {area_id} authentication".format(**area) + if area.get("authentication", {}).get("message_digest"): + command += " message-digest" + return command + + +def _tmplt_max_lsa(proc): + max_lsa = proc["max_lsa"] + command = "max-lsa {max_non_self_generated_lsa}".format(**max_lsa) + if max_lsa.get("threshold"): + command += " {threshold}".format(**max_lsa) + if max_lsa.get("warning_only"): + command += " warning-only" + if max_lsa.get("ignore_time"): + command += " ignore-time {ignore_time}".format(**max_lsa) + if max_lsa.get("ignore_count"): + command += " ignore-count {ignore_count}".format(**max_lsa) + if max_lsa.get("reset_time"): + command += " reset-time {reset_time}".format(**max_lsa) + return command + + +def _tmplt_default_information(proc): + default_information = proc["default_information"]["originate"] + command = "default-information originate" + + if default_information.get("set") is False: + command = "no {0}".format(command) + else: + if default_information.get("always"): + command += " always" + if default_information.get("route_map"): + command += " route-map {route_map}".format(**default_information) + + return command + + +def _tmplt_table_map(proc): + table_map = proc["table_map"] + command = "table-map" + + if table_map.get("name"): + command += " {name}".format(**table_map) + if table_map.get("filter"): + command += " filter" + + return command + + +def _tmplt_max_metric(proc): + max_metric = proc["max_metric"] + command = "max-metric router-lsa" + + if max_metric.get("router_lsa", {}).get("set") is False: + command = "no {0}".format(command) + else: + external_lsa = max_metric.get("router_lsa", {}).get("external_lsa", {}) + include_stub = max_metric.get("router_lsa", {}).get("include_stub", {}) + on_startup = max_metric.get("router_lsa", {}).get("on_startup", {}) + summary_lsa = max_metric.get("router_lsa", {}).get("summary_lsa", {}) + if external_lsa: + command += " external-lsa" + if external_lsa.get("max_metric_value"): + command += " {max_metric_value}".format(**external_lsa) + if include_stub: + command += " include-stub" + if on_startup: + command += " on-startup" + if on_startup.get("wait_period"): + command += " {wait_period}".format(**on_startup) + if on_startup.get("wait_for_bgp_asn"): + command += " wait-for bgp {wait_for_bgp_asn}".format(**on_startup) + if summary_lsa: + command += " summary-lsa" + if summary_lsa.get("max_metric_value"): + command += " {max_metric_value}".format(**summary_lsa) + + return command + + +def _tmplt_area_nssa(area): + nssa = area["nssa"] + command = "area {area_id} nssa".format(**area) + if nssa.get("set") is False: + command = "no {0}".format(command) + else: + for attrib in [ + "no_summary", + "no_redistribution", + "default_information_originate", + ]: + if nssa.get(attrib): + command += " {0}".format(attrib.replace("_", "-")) + return command + + +def _tmplt_area_nssa_translate(area): + translate = area["nssa"]["translate"]["type7"] + command = "area {area_id} nssa translate type7".format(**area) + for attrib in ["always", "never", "supress_fa"]: + if translate.get(attrib): + command += " {0}".format(attrib.replace("_", "-")) + return command + + +def _tmplt_area_ranges(arange): + command = "area {area_id} range {prefix}".format(**arange) + if arange.get("not_advertise") is True: + command += " not-advertise" + if "cost" in arange: + command += " cost {cost}".format(**arange) + return command + + +def _tmplt_area_ranges(arange): + command = "area {area_id} range {prefix}".format(**arange) + if arange.get("not_advertise") is True: + command += " not-advertise" + if "cost" in arange: + command += " cost {cost}".format(**arange) + return command + + +def _tmplt_summary_address(proc): + command = "summary-address {prefix}".format(**proc) + if proc.get("tag"): + command += " tag {tag}".format(**proc) + elif proc.get("not_advertise"): + command += " not-advertise" + return command + + +def _tmplt_area_stub(area): + stub = area["stub"] + command = "area {area_id} stub".format(**area) + if stub.get("set") is False: + command = "no {0}".format(command) + elif stub.get("no_summary"): + command += " no-summary" + return command + + +def _tmplt_redistribute(redis): + command = "redistribute {protocol}".format(**redis) + if redis.get("id"): + command += " {id}".format(**redis) + if redis.get("route_map"): + command += " route-map {route_map}".format(**redis) + return command + + +def _tmplt_capability_vrf_lite(proc): + command = "capability vrf-lite" + vrf_lite = proc["capability"]["vrf_lite"] + if vrf_lite.get("set") is False: + command = "no {0}".format(command) + else: + if vrf_lite.get("evpn"): + command += " evpn" + return command + + +class Ospfv2Template(NetworkTemplate): + def __init__(self, lines=None): + super(Ospfv2Template, self).__init__(lines=lines, tmplt=self) + + # fmt: off + PARSERS = [ + { + "name": "vrf", + "getval": re.compile( + r""" + \s+vrf + \s(?P<vrf>\S+)$""", + re.VERBOSE, + ), + "setval": "vrf {{ vrf }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "vrf": "{{ vrf }}", + }, + }, + }, + "shared": True, + }, + { + "name": "bfd", + "getval": re.compile( + r""" + \s+(?P<bfd>bfd)$""", + re.VERBOSE, + ), + "setval": "bfd", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bfd": "{{ not not bfd }}", + }, + }, + }, + }, + { + "name": "process_id", + "getval": re.compile( + r""" + ospf(?:v3)*\s + (?P<process_id>\S+)""", + re.VERBOSE, + ), + "setval": "router ospf {{ process_id }}", + "result": { + "process_id": "{{ process_id }}", + }, + "shared": True, + }, + { + "name": "down_bit_ignore", + "getval": re.compile( + r""" + \s+(?P<down_bit_ignore>down-bit-ignore)$""", + re.VERBOSE, + ), + "setval": "down-bit-ignore", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "down_bit_ignore": "{{ not not down_bit_ignore }}", + }, + }, + }, + }, + { + "name": "capability.vrf_lite", + "getval": re.compile( + r""" + \s+capability + \s(?P<vrf_lite>vrf-lite) + \s*(?P<evpn>evpn)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_capability_vrf_lite, + "remval": "capability vrf-lite", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "capability": { + "vrf_lite": { + "set": "{{ True if vrf_lite is defined and evpn is undefined else None }}", + "evpn": "{{ not not evpn }}", + }, + }, + }, + }, + }, + }, + { + "name": "auto_cost", + "getval": re.compile( + r""" + \s+auto-cost\sreference-bandwidth\s + (?P<acrb>\d+)\s(?P<unit>\S+)$""", + re.VERBOSE, + ), + "setval": ( + "auto-cost reference-bandwidth" + " {{ auto_cost.reference_bandwidth }}" + " {{ auto_cost.unit }}" + ), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "auto_cost": { + "reference_bandwidth": "{{ acrb }}", + "unit": "{{ unit }}", + }, + }, + }, + }, + }, + { + "name": "flush_routes", + "getval": re.compile( + r""" + \s+(?P<flush_routes>flush-routes)$""", + re.VERBOSE, + ), + "setval": "flush-routes", + "result": { + "flush_routes": "{{ not not flush_routes }}", + }, + }, + { + "name": "graceful_restart.set", + "getval": re.compile( + r""" + \s+(?P<graceful_restart>no\sgraceful-restart) + $""", + re.VERBOSE, + ), + "setval": "graceful-restart", + "remval": "no graceful-restart", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "set": "{{ not graceful_restart }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart.helper_disable", + "getval": re.compile( + r""" + \s+graceful-restart + \s+(?P<helper_disable>helper-disable) + $""", + re.VERBOSE, + ), + "setval": "graceful-restart helper-disable", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "helper_disable": "{{ not not helper_disable }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart.grace_period", + "getval": re.compile( + r""" + \s+graceful-restart + \s+grace-period + \s+(?P<grace_period>\d+) + $""", + re.VERBOSE, + ), + "setval": "graceful-restart helper-disable", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "grace_period": "{{ grace_period }}", + }, + }, + }, + }, + }, + { + "name": "isolate", + "getval": re.compile( + r""" + \s+(?P<isolate>isolate)$""", + re.VERBOSE, + ), + "setval": "isolate", + "result": {"isolate": "{{ not not isolate }}"}, + }, + { + "name": "log_adjacency_changes", + "getval": re.compile( + r""" + \s+(?P<log>log-adjacency-changes) + \s*(?P<detail>detail)*$""", + re.VERBOSE, + ), + "setval": _tmplt_log_adjacency_changes, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "log_adjacency_changes": { + "log": "{{ True if log is defined and detail is undefined else None }}", + "detail": "{{ True if detail is defined else None }}", + }, + }, + }, + }, + }, + { + "name": "max_lsa", + "getval": re.compile( + r""" + \s+max-lsa + \s(?P<max_gen_lsa>\d+) + \s*(?P<threshold>\d*) + \s*(?P<warning_only>warning-only)* + \s*(ignore-time)*\s*(?P<ig_time>\d*) + \s*(ignore-count)*\s*(?P<ig_count>\d*) + \s*(reset-time)*\s*(?P<rst_time>\d*) + $""", + re.VERBOSE, + ), + "setval": _tmplt_max_lsa, + "remval": "max-lsa {{ max_lsa.max_non_self_generated_lsa }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "max_lsa": { + "max_non_self_generated_lsa": "{{ max_gen_lsa }}", + "threshold": "{{ threshold }}", + "ignore_time": "{{ ig_time }}", + "ignore_count": "{{ ig_count }}", + "reset_time": "{{ rst_time }}", + "warning_only": "{{ not not warning_only }}", + }, + }, + }, + }, + }, + { + "name": "mpls.traffic_eng.areas", + "getval": re.compile( + r""" + \s+mpls\straffic-eng\sarea + \s(?P<area_id>\S+)$""", + re.VERBOSE, + ), + "setval": ("mpls traffic-eng area {{ area_id }}"), + "result": { + "mpls": { + "traffic_eng": { + "areas": [ + { + "area_id": "{{ area_id }}", + }, + ], + }, + }, + }, + }, + { + "name": "mpls.traffic_eng.router_id", + "getval": re.compile( + r""" + \s+mpls\straffic-eng\srouter-id + \s(?P<router_id>\S+) + $""", + re.VERBOSE, + ), + "setval": ( + "mpls traffic-eng router-id" " {{ mpls.traffic_eng.router_id }}" + ), + "result": {"mpls": {"traffic_eng": {"router_id": "{{ router_id }}"}}}, + }, + { + "name": "mpls.traffic_eng.multicast_intact", + "getval": re.compile( + r""" + \s+mpls\straffic-eng + \s(?P<multicast_intact>multicast-intact) + $""", + re.VERBOSE, + ), + "setval": ("mpls traffic-eng multicast-intact"), + "result": { + "mpls": { + "traffic_eng": { + "multicast_intact": "{{ not not multicast_intact }}", + }, + }, + }, + }, + { + "name": "name_lookup", + "getval": re.compile( + r""" + \s+(?P<name_lookup>name-lookup) + $""", + re.VERBOSE, + ), + "setval": ("name-lookup"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "name_lookup": "{{ not not name_lookup }}", + }, + }, + }, + }, + { + "name": "passive_interface.default", + "getval": re.compile( + r""" + \s+passive-interface + \s+(?P<default>default) + $""", + re.VERBOSE, + ), + "setval": ("passive-interface default"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "passive_interface": {"default": "{{ not not default }}"}, + }, + }, + }, + }, + { + "name": "rfc1583compatibility", + "getval": re.compile( + r""" + \s+(?P<rfc>rfc1583compatibility)$""", + re.VERBOSE, + ), + "setval": ("rfc1583compatibility"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "rfc1583compatibility": "{{ not not rfc }}", + }, + }, + }, + }, + { + "name": "router_id", + "getval": re.compile( + r""" + \s+router-id + \s(?P<router_id>\S+)$""", + re.VERBOSE, + ), + "setval": ("router-id" " {{ router_id }}"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "router_id": "{{ router_id }}", + }, + }, + }, + }, + { + "name": "shutdown", + "getval": re.compile( + r""" + \s+(?P<shutdown>shutdown)$""", + re.VERBOSE, + ), + "setval": ("shutdown"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "shutdown": "{{ not not shutdown }}", + }, + }, + }, + }, + { + "name": "default_information.originate", + "getval": re.compile( + r""" + \s+default-information + \s(?P<originate>originate) + \s*(?P<always>always)* + \s*(route-map)* + \s*(?P<route_map>\S+)*$""", + re.VERBOSE, + ), + "setval": _tmplt_default_information, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "default_information": { + "originate": { + "set": "{{ True if originate is defined and always is undefined and route_map is undefined else None }}", + "always": "{{ not not always }}", + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + }, + { + "name": "default_metric", + "getval": re.compile( + r""" + \s+default-metric + \s(?P<default_metric>\d+)$""", + re.VERBOSE, + ), + "setval": ("default-metric {{ default_metric }}"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "default_metric": "{{ default_metric }}", + }, + }, + }, + }, + { + "name": "distance", + "getval": re.compile( + r""" + \s+distance + \s(?P<distance>\d+)$""", + re.VERBOSE, + ), + "setval": ("distance {{ distance }}"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "distance": "{{ distance }}", + }, + }, + }, + }, + { + "name": "table_map", + "getval": re.compile( + r""" + \s+table-map + \s(?P<rmap>\S+) + \s*(?P<filter>filter)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_table_map, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "table_map": { + "name": "{{ rmap }}", + "filter": "{{ not not filter }}", + }, + }, + }, + }, + }, + { + "name": "timers.lsa_arrival", + "getval": re.compile( + r""" + \s+timers + \slsa-arrival + \s(?P<lsa_arrival_val>\d+) + $""", + re.VERBOSE, + ), + "setval": ("timers lsa-arrival {{ timers.lsa_arrival }}"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "lsa_arrival": "{{ lsa_arrival_val }}", + }, + }, + }, + }, + }, + { + "name": "timers.lsa_group_pacing", + "getval": re.compile( + r""" + \s+timers + \slsa-group-pacing + \s(?P<lsa_group_pacing>\d+) + $""", + re.VERBOSE, + ), + "setval": "timers lsa-group-pacing {{ timers.lsa_group_pacing }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "lsa_group_pacing": "{{ lsa_group_pacing }}", + }, + }, + }, + }, + }, + { + "name": "timers.throttle.lsa", + "getval": re.compile( + r""" + \s+timers\sthrottle\slsa + \s(?P<start>\d+) + \s(?P<hold>\d+) + \s(?P<max>\d+) + $""", + re.VERBOSE, + ), + "setval": "timers throttle lsa {{ timers.throttle.lsa.start_interval }}" + " {{ timers.throttle.lsa.hold_interval }}" + " {{ timers.throttle.lsa.max_interval }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "throttle": { + "lsa": { + "start_interval": "{{ start }}", + "hold_interval": "{{ hold }}", + "max_interval": "{{ max }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "timers.throttle.spf", + "getval": re.compile( + r""" + \s+timers\sthrottle\sspf + \s(?P<initial>\d+) + \s(?P<min>\d+) + \s(?P<max>\d+) + $""", + re.VERBOSE, + ), + "setval": "timers throttle spf {{ timers.throttle.spf.initial_spf_delay }}" + " {{ timers.throttle.spf.min_hold_time }}" + " {{ timers.throttle.spf.max_wait_time }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "throttle": { + "spf": { + "initial_spf_delay": "{{ initial }}", + "min_hold_time": "{{ min }}", + "max_wait_time": "{{ max }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "area.default_cost", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+)\s + default-cost\s(?P<default_cost>\d+) + $""", + re.VERBOSE, + ), + "setval": "area {{ area_id }} default-cost {{ default_cost }}", + "compval": "default_cost", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "default_cost": "{{ default_cost|int }}", + }, + }, + }, + }, + }, + }, + { + "name": "area.authentication", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \s(?P<auth>authentication) + \s*(?P<md>message-digest)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_authentication, + "remval": "area {{ area_id }} authentication", + "compval": "authentication", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "authentication": { + "set": "{{ True if auth is defined and md is undefined }}", + "message_digest": "{{ True if md is defined else False }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "area.filter_list", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \sfilter-list + \sroute-map\s(?P<rmap>\S+) + \s(?P<dir>\S+)$""", + re.VERBOSE, + ), + "setval": "area {{ area_id }} filter-list route-map {{ route_map }} {{ direction }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "filter_list": [ + { + "route_map": "{{ rmap }}", + "direction": "{{ dir }}", + }, + ], + }, + }, + }, + }, + }, + }, + { + "name": "redistribute", + "getval": re.compile( + r""" + \s+redistribute + \s(?P<protocol>\S+) + \s*(?P<id>\S+)* + \sroute-map\s(?P<rmap>\S+) + $""", + re.VERBOSE, + ), + "setval": _tmplt_redistribute, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "redistribute": [ + { + "protocol": "{{ protocol }}", + "id": "{{ id }}", + "route_map": "{{ rmap }}", + }, + ], + }, + }, + }, + }, + { + "name": "area.nssa", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \s(?P<nssa>nssa) + \s*(?P<no_sum>no-summary)* + \s*(?P<no_redis>no-redistribution)* + \s*(?P<def_info>default-information-originate)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_nssa, + "remval": "area {{ area_id }} nssa", + "compval": "nssa", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "nssa": { + "set": "{{ True if nssa is defined and no_sum is undefined and no_redis is undefined and def_info is undefined }}", + "no_summary": "{{ not not no_sum }}", + "no_redistribution": "{{ not not no_redis }}", + "default_information_originate": "{{ not not def_info }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "area.nssa.translate", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+)\snssa + \stranslate + \stype7 + \s(?P<choice>always|never) + \s*(?P<supress_fa>supress-fa)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_nssa_translate, + "compval": "nssa.translate", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "nssa": { + "translate": { + "type7": { + "always": '{{ True if choice == "always" else None }}', + "never": '{{ True if choice == "never" else None }}', + "supress_fa": "{{ not not supress_fa }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "area.ranges", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \srange\s(?P<prefix>\S+) + \s*(cost)*\s*(?P<cost>\d+)* + \s*(?P<not_adver>not-advertise)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_ranges, + "remval": "area {{ area_id }} range {{ prefix }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "ranges": [ + { + "prefix": "{{ prefix }}", + "cost": "{{ cost }}", + "not_advertise": "{{ not not not_adver }}", + }, + ], + }, + }, + }, + }, + }, + }, + { + "name": "summary_address", + "getval": re.compile( + r""" + \s+summary-address + \s(?P<prefix>\S+) + \s*(?P<not_adver>not-advertise)* + \s*(tag)*\s*(?P<tag>\d+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_summary_address, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "summary_address": [ + { + "prefix": "{{ prefix }}", + "not_advertise": "{{ not not not_adver }}", + "tag": "{{ tag }}", + }, + ], + }, + }, + }, + }, + { + "name": "area.stub", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \s(?P<stub>stub) + \s*(?P<no_summary>no-summary)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_stub, + "remval": "area {{ area_id }} stub", + "compval": "stub", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "stub": { + "set": "{{ True if stub is defined and no_summary is undefined else None }}", + "no_summary": "{{ not not no_summary }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "maximum_paths", + "getval": re.compile( + r""" + \s+maximum-paths + \s(?P<maximum_paths>\d+)$""", + re.VERBOSE, + ), + "setval": ("maximum-paths {{ maximum_paths }}"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': {"maximum_paths": "{{ maximum_paths }}"}, + }, + }, + }, + { + "name": "max_metric", + "getval": re.compile( + r""" + \s+max-metric + \s+(?P<router_lsa>router-lsa) + \s*(?P<external_lsa>external-lsa)* + \s*(?P<max_metric_value>\d+)* + \s*(?P<include_stub>include-stub)* + \s*(?P<on_startup>on-startup)* + \s*(?P<wait_period>\d+)* + \s*(wait-for\sbgp)* + \s*(?P<bgp_asn>\d+)* + \s*(?P<summary_lsa>summary-lsa)* + \s*(?P<sum_lsa_max_metric_value>\d+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_max_metric, + "remval": "max-metric router-lsa", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "max_metric": { + "router_lsa": { + "set": "{{ True if router_lsa is defined and external_lsa is undefined else None }}", + "external_lsa": { + "set": "{{ True if external_lsa is defined and max_metric_value is undefined else None }}", + "max_metric_value": "{{ max_metric_value }}", + }, + "include_stub": "{{ not not include_stub }}", + "on_startup": { + "set": "{{ True if on_startup is defined and (wait_period and bgp_asn) is undefined else None }}", + "wait_period": "{{ wait_period }}", + "wait_for_bgp_asn": "{{ bgp_asn }}", + }, + "summary_lsa": { + "set": "{{ True if summary_lsa is defined and sum_lsa_max_metric_value is undefined else None }}", + "max_metric_value": "{{ sum_lsa_max_metric_value }}", + }, + }, + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv3.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv3.py new file mode 100644 index 00000000..b46c3cf4 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv3.py @@ -0,0 +1,945 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# 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 + +""" +The Ospfv3 parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_area_nssa(area): + nssa = area["nssa"] + command = "area {area_id} nssa".format(**area) + if nssa.get("set") is False: + command = "no {0}".format(command) + else: + for attrib in [ + "no_summary", + "no_redistribution", + "default_information_originate", + ]: + if nssa.get(attrib): + command += " {0}".format(attrib.replace("_", "-")) + if nssa.get("route_map"): + command += " route-map {route_map}".format(**nssa) + return command + + +def _tmplt_area_nssa_translate(area): + translate = area["nssa"]["translate"]["type7"] + command = "area {area_id} nssa translate type7".format(**area) + for attrib in ["always", "never", "supress_fa"]: + if translate.get(attrib): + command += " {0}".format(attrib.replace("_", "-")) + return command + + +def _tmplt_area_stub(area): + stub = area["stub"] + command = "area {area_id} stub".format(**area) + if stub.get("set") is False: + command = "no {0}".format(command) + elif stub.get("no_summary"): + command += " no-summary" + return command + + +def _tmplt_log_adjacency_changes(proc): + command = "log-adjacency-changes" + if proc.get("log_adjacency_changes").get("detail", False) is True: + command += " detail" + return command + + +def _tmplt_max_lsa(proc): + max_lsa = proc["max_lsa"] + command = "max-lsa {max_non_self_generated_lsa}".format(**max_lsa) + if max_lsa.get("threshold"): + command += " {threshold}".format(**max_lsa) + if max_lsa.get("warning_only"): + command += " warning-only" + if max_lsa.get("ignore_time"): + command += " ignore-time {ignore_time}".format(**max_lsa) + if max_lsa.get("ignore_count"): + command += " ignore-count {ignore_count}".format(**max_lsa) + if max_lsa.get("reset_time"): + command += " reset-time {reset_time}".format(**max_lsa) + return command + + +def _tmplt_max_metric(proc): + max_metric = proc["max_metric"] + command = "max-metric router-lsa" + + if max_metric.get("router_lsa", {}).get("set") is False: + command = "no {0}".format(command) + else: + external_lsa = max_metric.get("router_lsa", {}).get("external_lsa", {}) + stub_prefix_lsa = max_metric.get("router_lsa", {}).get("stub_prefix_lsa", {}) + on_startup = max_metric.get("router_lsa", {}).get("on_startup", {}) + inter_area_prefix_lsa = max_metric.get("router_lsa", {}).get("inter_area_prefix_lsa", {}) + if external_lsa: + command += " external-lsa" + if external_lsa.get("max_metric_value"): + command += " {max_metric_value}".format(**external_lsa) + if stub_prefix_lsa: + command += " stub-prefix-lsa" + if on_startup: + command += " on-startup" + if on_startup.get("wait_period"): + command += " {wait_period}".format(**on_startup) + if on_startup.get("wait_for_bgp_asn"): + command += " wait-for bgp {wait_for_bgp_asn}".format(**on_startup) + if inter_area_prefix_lsa: + command += " inter-area-prefix-lsa" + if inter_area_prefix_lsa.get("max_metric_value"): + command += " {max_metric_value}".format(**inter_area_prefix_lsa) + + return command + + +def _tmplt_area_ranges(arange): + command = "area {area_id} range {prefix}".format(**arange) + if arange.get("not_advertise") is True: + command += " not-advertise" + if "cost" in arange: + command += " cost {cost}".format(**arange) + return command + + +def _tmplt_default_information(proc): + default_information = proc["default_information"]["originate"] + command = "default-information originate" + + if default_information.get("set") is False: + command = "no {0}".format(command) + else: + if default_information.get("always"): + command += " always" + if default_information.get("route_map"): + command += " route-map {route_map}".format(**default_information) + + return command + + +def _tmplt_redistribute(redis): + command = "redistribute {protocol}".format(**redis) + if redis.get("id"): + command += " {id}".format(**redis) + command += " route-map {route_map}".format(**redis) + return command + + +def _tmplt_summary_address(proc): + command = "summary-address {prefix}".format(**proc) + if proc.get("tag"): + command += " tag {tag}".format(**proc) + elif proc.get("not_advertise"): + command += " not-advertise" + return command + + +def _tmplt_table_map(proc): + table_map = proc["table_map"] + command = "table-map {name}".format(**table_map) + if table_map.get("filter"): + command += " filter" + + return command + + +class Ospfv3Template(NetworkTemplate): + def __init__(self, lines=None): + super(Ospfv3Template, self).__init__(lines=lines, tmplt=self) + + # fmt: off + PARSERS = [ + { + "name": "vrf", + "getval": re.compile( + r""" + \s+vrf + \s(?P<vrf>\S+)$""", + re.VERBOSE, + ), + "setval": "vrf {{ vrf }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "vrf": "{{ vrf }}", + }, + }, + }, + "shared": True, + }, + { + "name": "process_id", + "getval": re.compile( + r""" + ospfv3 + \s(?P<process_id>\S+)""", + re.VERBOSE, + ), + "setval": "router ospfv3 {{ process_id }}", + "result": { + "process_id": "{{ process_id }}", + }, + "shared": True, + }, + { + "name": "router_id", + "getval": re.compile( + r""" + \s+router-id + \s(?P<router_id>\S+)$""", + re.VERBOSE, + ), + "setval": "router-id {{ router_id }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "router_id": "{{ router_id }}", + }, + }, + }, + }, + { + "name": "address_family", + "getval": re.compile( + r""" + \s+address-family + \s(?P<afi>\S+) + \s(?P<safi>\S+) + $""", + re.VERBOSE, + ), + "setval": "address-family {{ afi }} {{ safi }}", + "result": { + "address_family": { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + }, + }, + 'shared': True, + }, + { + "name": "area.default_cost", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+)\s + default-cost\s(?P<default_cost>\d+) + $""", + re.VERBOSE, + ), + "setval": "area {{ area_id }} default-cost {{ default_cost }}", + "compval": "default_cost", + "result": { + 'address_family': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "default_cost": "{{ default_cost|int }}", + }, + }, + }, + }, + }, + { + "name": "area.filter_list", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \sfilter-list + \sroute-map\s(?P<rmap>\S+) + \s(?P<dir>\S+)$""", + re.VERBOSE, + ), + "setval": "area {{ area_id }} filter-list route-map {{ route_map }} {{ direction }}", + "result": { + 'address_family': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "filter_list": [ + { + "route_map": "{{ rmap }}", + "direction": "{{ dir }}", + }, + ], + }, + }, + }, + }, + }, + { + "name": "area.ranges", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \srange\s(?P<prefix>\S+) + \s*(cost)*\s*(?P<cost>\d+)* + \s*(?P<not_adver>not-advertise)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_ranges, + "remval": "area {{ area_id }} range {{ prefix }}", + "result": { + "address_family": { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "ranges": [ + { + "prefix": "{{ prefix }}", + "cost": "{{ cost }}", + "not_advertise": "{{ not not not_adver }}", + }, + ], + }, + }, + }, + }, + }, + { + "name": "default_information.originate", + "getval": re.compile( + r""" + \s+default-information + \s(?P<originate>originate) + \s*(?P<always>always)* + \s*(route-map)* + \s*(?P<route_map>\S+)*$""", + re.VERBOSE, + ), + "setval": _tmplt_default_information, + "result": { + "address_family": { + "default_information": { + "originate": { + "set": "{{ True if originate is defined and always is undefined and route_map is undefined else None }}", + "always": "{{ not not always }}", + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + { + "name": "distance", + "getval": re.compile( + r""" + \s+distance + \s(?P<distance>\d+)$""", + re.VERBOSE, + ), + "setval": "distance {{ distance }}", + "result": { + "address_family": { + "distance": "{{ distance }}", + }, + }, + }, + { + "name": "maximum_paths", + "getval": re.compile( + r""" + \s+maximum-paths + \s(?P<maximum_paths>\d+)$""", + re.VERBOSE, + ), + "setval": ("maximum-paths {{ maximum_paths }}"), + "result": { + "address_family": { + "maximum_paths": "{{ maximum_paths }}", + }, + }, + }, + { + "name": "redistribute", + "getval": re.compile( + r""" + \s+redistribute + \s(?P<protocol>\S+) + \s*(?P<id>\S+)* + \sroute-map\s(?P<rmap>\S+) + $""", + re.VERBOSE, + ), + "setval": _tmplt_redistribute, + "result": { + "address_family": { + "redistribute": [ + { + "protocol": "{{ protocol }}", + "id": "{{ id }}", + "route_map": "{{ rmap }}", + }, + ], + }, + }, + }, + { + "name": "summary_address", + "getval": re.compile( + r""" + \s+summary-address + \s(?P<prefix>\S+) + \s*(?P<not_adver>not-advertise)* + \s*(tag)*\s*(?P<tag>\d+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_summary_address, + "result": { + "address_family": { + "summary_address": [ + { + "prefix": "{{ prefix }}", + "not_advertise": "{{ not not not_adver }}", + "tag": "{{ tag }}", + }, + ], + }, + }, + }, + { + "name": "table_map", + "getval": re.compile( + r""" + \s+table-map + \s(?P<rmap>\S+) + \s*(?P<filter>filter)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_table_map, + "result": { + "address_family": { + "table_map": { + "name": "{{ rmap }}", + "filter": "{{ not not filter }}", + }, + }, + }, + }, + { + "name": "timers.throttle.spf", + "getval": re.compile( + r""" + \s+timers\sthrottle\sspf + \s(?P<initial>\d+) + \s(?P<min>\d+) + \s(?P<max>\d+) + $""", + re.VERBOSE, + ), + "setval": "timers throttle spf {{ timers.throttle.spf.initial_spf_delay }}" + " {{ timers.throttle.spf.min_hold_time }}" + " {{ timers.throttle.spf.max_wait_time }}", + "result": { + "address_family": { + "timers": { + "throttle": { + "spf": { + "initial_spf_delay": "{{ initial }}", + "min_hold_time": "{{ min }}", + "max_wait_time": "{{ max }}", + }, + }, + }, + }, + }, + }, + { + "name": "area.nssa", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \s(?P<nssa>nssa) + \s*(?P<no_sum>no-summary)* + \s*(?P<no_redis>no-redistribution)* + \s*(?P<def_info>default-information-originate)* + \s*(route-map)*\s*(?P<rmap>\S+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_nssa, + "remval": "area {{ area_id }} nssa", + "compval": "nssa", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "nssa": { + "set": "{{ True if nssa is defined and no_sum is undefined and no_redis is undefined and \ + def_info is undefined and rmap is undefined }}", + "no_summary": "{{ not not no_sum }}", + "no_redistribution": "{{ not not no_redis }}", + "default_information_originate": "{{ not not def_info }}", + "route_map": "{{ rmap }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "area.nssa.translate", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+)\snssa + \stranslate + \stype7 + \s(?P<choice>always|never) + \s*(?P<supress_fa>supress-fa)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_nssa_translate, + "compval": "nssa.translate", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "nssa": { + "translate": { + "type7": { + "always": '{{ True if choice == "always" else None }}', + "never": '{{ True if choice == "never" else None }}', + "supress_fa": "{{ not not supress_fa }}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "area.stub", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \s(?P<stub>stub) + \s*(?P<no_summary>no-summary)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_area_stub, + "remval": "area {{ area_id }} stub", + "compval": "stub", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "stub": { + "set": "{{ True if stub is defined and no_summary is undefined else None }}", + "no_summary": "{{ not not no_summary }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "area.virtual_link", + "getval": re.compile( + r""" + \s+area\s(?P<area_id>\S+) + \svirtual-link + \s(?P<virtual_link>\S+) + $""", + re.VERBOSE, + ), + "setval": "area {{ area_id }} virtual-link {{ virtual_link }}", + "compval": "virtual_link", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "areas": { + "{{ area_id }}": { + "area_id": "{{ area_id }}", + "virtual_link": "{{ virtual_link }}", + }, + }, + }, + }, + }, + }, + { + "name": "auto_cost", + "getval": re.compile( + r""" + \s+auto-cost\sreference-bandwidth\s + (?P<acrb>\d+)\s(?P<unit>\S+)$""", + re.VERBOSE, + ), + "setval": ( + "auto-cost reference-bandwidth" + " {{ auto_cost.reference_bandwidth }}" + " {{ auto_cost.unit }}" + ), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "auto_cost": { + "reference_bandwidth": "{{ acrb }}", + "unit": "{{ unit }}", + }, + }, + }, + }, + }, + { + "name": "flush_routes", + "getval": re.compile( + r""" + \s+(?P<flush_routes>flush-routes)$""", + re.VERBOSE, + ), + "setval": "flush-routes", + "result": { + "flush_routes": "{{ not not flush_routes }}", + }, + }, + { + "name": "graceful_restart.set", + "getval": re.compile( + r""" + \s+(?P<graceful_restart>no\sgraceful-restart) + $""", + re.VERBOSE, + ), + "setval": "graceful-restart", + "remval": "no graceful-restart", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "set": "{{ not graceful_restart }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart.helper_disable", + "getval": re.compile( + r""" + \s+graceful-restart + \s+(?P<helper_disable>helper-disable) + $""", + re.VERBOSE, + ), + "setval": "graceful-restart helper-disable", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "helper_disable": "{{ not not helper_disable }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart.grace_period", + "getval": re.compile( + r""" + \s+graceful-restart + \s+grace-period + \s+(?P<grace_period>\d+) + $""", + re.VERBOSE, + ), + "setval": "graceful-restart grace-period {{ graceful_restart.grace_period }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "grace_period": "{{ grace_period }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart.planned_only", + "getval": re.compile( + r""" + \s+no + \s+graceful-restart + \s+(?P<planned_only>planned-only) + $""", + re.VERBOSE, + ), + "setval": "graceful-restart planned-only", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "graceful_restart": { + "planned_only": "{{ not planned_only }}", + }, + }, + }, + }, + }, + { + "name": "isolate", + "getval": re.compile( + r""" + \s+(?P<isolate>isolate)$""", + re.VERBOSE, + ), + "setval": "isolate", + "result": {"isolate": "{{ not not isolate }}"}, + }, + { + "name": "log_adjacency_changes", + "getval": re.compile( + r""" + \s+(?P<log>log-adjacency-changes) + \s*(?P<detail>detail)*$""", + re.VERBOSE, + ), + "setval": _tmplt_log_adjacency_changes, + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "log_adjacency_changes": { + "log": "{{ True if log is defined and detail is undefined else None }}", + "detail": "{{ True if detail is defined else None }}", + }, + }, + }, + }, + }, + { + + "name": "max_lsa", + "getval": re.compile( + r""" + \s+max-lsa + \s(?P<max_gen_lsa>\d+) + \s*(?P<threshold>\d*) + \s*(?P<warning_only>warning-only)* + \s*(ignore-time)*\s*(?P<ig_time>\d*) + \s*(ignore-count)*\s*(?P<ig_count>\d*) + \s*(reset-time)*\s*(?P<rst_time>\d*) + $""", + re.VERBOSE, + ), + "setval": _tmplt_max_lsa, + "remval": "max-lsa {{ max_lsa.max_non_self_generated_lsa }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "max_lsa": { + "max_non_self_generated_lsa": "{{ max_gen_lsa }}", + "threshold": "{{ threshold }}", + "ignore_time": "{{ ig_time }}", + "ignore_count": "{{ ig_count }}", + "reset_time": "{{ rst_time }}", + "warning_only": "{{ not not warning_only }}", + }, + }, + }, + }, + }, + { + "name": "max_metric", + "getval": re.compile( + r""" + \s+max-metric + \s+(?P<router_lsa>router-lsa) + \s*(?P<external_lsa>external-lsa)* + \s*(?P<max_metric_value>\d+)* + \s*(?P<stub_prefix_lsa>stub-prefix-lsa)* + \s*(?P<on_startup>on-startup)* + \s*(?P<wait_period>\d+)* + \s*(wait-for\sbgp)* + \s*(?P<bgp_asn>\d+)* + \s*(?P<inter_area_prefix_lsa>inter-area-prefix-lsa)* + \s*(?P<max_metric_summary_lsa>\d+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_max_metric, + "remval": "max-metric router-lsa", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "max_metric": { + "router_lsa": { + "set": "{{ True if router_lsa is defined and (external_lsa is undefined) and (inter_area_prefix_lsa is undefined) and \ + (stub_prefix_lsa is undefined) and (on_startup is undefined) else None }}", + "external_lsa": { + "set": "{{ True if external_lsa is defined and max_metric_value is undefined else None }}", + "max_metric_value": "{{ max_metric_value }}", + }, + "stub_prefix_lsa": "{{ not not stub_prefix_lsa }}", + "on_startup": { + "set": "{{ True if on_startup is defined and (wait_period and bgp_asn) is undefined else None }}", + "wait_period": "{{ wait_period }}", + "wait_for_bgp_asn": "{{ bgp_asn }}", + }, + "inter_area_prefix_lsa": { + "set": "{{ True if inter_area_prefix_lsa is defined and max_metric_summary_lsa is undefined else None }}", + "max_metric_value": "{{ max_metric_summary_lsa }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "name_lookup", + "getval": re.compile( + r""" + \s+(?P<name_lookup>name-lookup) + $""", + re.VERBOSE, + ), + "setval": ("name-lookup"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "name_lookup": "{{ not not name_lookup }}", + }, + }, + }, + }, + { + "name": "passive_interface.default", + "getval": re.compile( + r""" + \s+passive-interface + \s+(?P<default>default) + $""", + re.VERBOSE, + ), + "setval": ("passive-interface default"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "passive_interface": {"default": "{{ not not default }}"}, + }, + }, + }, + }, + { + "name": "shutdown", + "getval": re.compile( + r""" + \s+(?P<shutdown>shutdown)$""", + re.VERBOSE, + ), + "setval": ("shutdown"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "shutdown": "{{ not not shutdown }}", + }, + }, + }, + }, + { + "name": "timers.lsa_arrival", + "getval": re.compile( + r""" + \s+timers + \slsa-arrival + \s(?P<lsa_arrival_val>\d+) + $""", + re.VERBOSE, + ), + "setval": ("timers lsa-arrival {{ timers.lsa_arrival }}"), + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "lsa_arrival": "{{ lsa_arrival_val }}", + }, + }, + }, + }, + }, + { + "name": "timers.lsa_group_pacing", + "getval": re.compile( + r""" + \s+timers + \slsa-group-pacing + \s(?P<lsa_group_pacing>\d+) + $""", + re.VERBOSE, + ), + "setval": "timers lsa-group-pacing {{ timers.lsa_group_pacing }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "lsa_group_pacing": "{{ lsa_group_pacing }}", + }, + }, + }, + }, + }, + { + "name": "timers.throttle.lsa", + "getval": re.compile( + r""" + \s+timers\sthrottle\slsa + \s(?P<start>\d+) + \s(?P<hold>\d+) + \s(?P<max>\d+) + $""", + re.VERBOSE, + ), + "setval": "timers throttle lsa {{ timers.throttle.lsa.start_interval }}" + " {{ timers.throttle.lsa.hold_interval }}" + " {{ timers.throttle.lsa.max_interval }}", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "timers": { + "throttle": { + "lsa": { + "start_interval": "{{ start }}", + "hold_interval": "{{ hold }}", + "max_interval": "{{ max }}", + }, + }, + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/prefix_lists.py new file mode 100644 index 00000000..0bb66ead --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/prefix_lists.py @@ -0,0 +1,102 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Prefix_lists parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +class Prefix_listsTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Prefix_listsTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "entry", + "getval": re.compile( + r""" + ^(?P<afi>ip|ipv6) + \sprefix-list + \s(?P<name>\S+) + \sseq\s(?P<sequence>\d+) + \s(?P<action>permit|deny) + \s(?P<prefix>\S+) + (\seq\s(?P<eq>\d+))? + (\sge\s(?P<ge>\d+))? + (\sle\s(?P<le>\d+))? + (\smask\s(?P<mask>\S+))? + \s* + $""", re.VERBOSE, + ), + "setval": "{{ 'ip' if afi == 'ipv4' else afi }} prefix-list {{ name }}" + "{{ (' seq ' + sequence|string) if sequence|d('') else '' }}" + " {{ action }}" + " {{ prefix }}" + "{{ (' eq ' + eq|string) if eq|d('') else '' }}" + "{{ (' ge ' + ge|string) if ge|d('') else '' }}" + "{{ (' le ' + le|string) if le|d('') else '' }}" + "{{ (' mask ' + mask) if mask|d('') else '' }}", + "result": { + "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "entries": [ + { + "sequence": "{{ sequence|d(None) }}", + "action": "{{ action }}", + "prefix": "{{ prefix }}", + "eq": "{{ eq }}", + "ge": "{{ ge }}", + "le": "{{ le }}", + "mask": "{{ mask }}", + }, + ], + }, + }, + }, + }, + }, + { + "name": "description", + "getval": re.compile( + r""" + ^(?P<afi>ip|ipv6) + \sprefix-list + \s(?P<name>\S+) + \sdescription\s(?P<description>.+)\s* + $""", re.VERBOSE, + ), + "setval": "{{ 'ip' if afi == 'ipv4' else afi }} prefix-list {{ name }} description {{ description }}", + "result": { + "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "description": "{{ description }}", + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/route_maps.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/route_maps.py new file mode 100644 index 00000000..8267a905 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/route_maps.py @@ -0,0 +1,1367 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Route_maps parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_match_ip_multicast(data): + cmd = "match ip multicast" + multicast = data["match"]["ip"]["multicast"] + + if "source" in multicast: + cmd += " source {source}".format(**multicast) + + if "prefix" in multicast.get("group", {}): + cmd += " group {prefix}".format(**multicast["group"]) + else: + if "first" in multicast.get("group_range", {}): + cmd += " group-range {first}".format(**multicast["group_range"]) + if "last" in multicast.get("group_range", {}): + cmd += " to {last}".format(**multicast["group_range"]) + + if "rp" in multicast: + cmd += " rp {prefix}".format(**multicast["rp"]) + if "rp_type" in multicast["rp"]: + cmd += " rp-type {rp_type}".format(**multicast["rp"]) + + return cmd + + +def _tmplt_match_ipv6_multicast(data): + cmd = "match ipv6 multicast" + multicast = data["match"]["ipv6"]["multicast"] + + if "source" in multicast: + cmd += " source {source}".format(**multicast) + + if "prefix" in multicast.get("group", {}): + cmd += " group {prefix}".format(**multicast["group"]) + else: + if "first" in multicast.get("group_range", {}): + cmd += " group-range {first}".format(**multicast["group_range"]) + if "last" in multicast.get("group_range", {}): + cmd += " to {last}".format(**multicast["group_range"]) + + if "rp" in multicast: + cmd += " rp {prefix}".format(**multicast["rp"]) + if "rp_type" in multicast["rp"]: + cmd += " rp-type {rp_type}".format(**multicast["rp"]) + + return cmd + + +def _tmplt_set_metric(data): + cmd = "set metric" + metric = data["set"]["metric"] + + for x in [ + "bandwidth", + "igrp_delay_metric", + "igrp_reliability_metric", + "igrp_effective_bandwidth_metric", + "igrp_mtu", + ]: + if x in metric: + cmd += " {0}".format(metric[x]) + + return cmd + + +class Route_mapsTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Route_mapsTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "route_map", + "getval": re.compile( + r""" + ^route-map\s(?P<route_map>\S+)\s(?P<action>\S+)\s(?P<sequence>\d+) + $""", re.VERBOSE, + ), + "setval": "route-map {{ route_map }}" + "{{ ' ' + action if action is defined else '' }}" + "{{ ' ' + sequence|string if sequence is defined else '' }}", + "result": { + "{{ route_map }}": { + "route_map": "{{ route_map }}", + "entries": { + "{{ sequence }}": { + "sequence": "{{ sequence }}", + "action": "{{ action }}", + }, + }, + }, + }, + "shared": True, + }, + { + "name": "continue_sequence", + "getval": re.compile( + r""" + \s+continue\s(?P<continue_sequence>\d+) + $""", re.VERBOSE, + ), + "setval": "continue {{ continue_sequence }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "continue_sequence": "{{ continue_sequence }}", + }, + }, + }, + }, + }, + { + "name": "description", + "getval": re.compile( + r""" + \s+description\s(?P<description>\S+) + $""", re.VERBOSE, + ), + "setval": "description {{ description }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "description": "{{ description }}", + }, + }, + }, + }, + }, + { + "name": "match.as_number.asn", + "getval": re.compile( + r""" + \s+match\sas-number + (?!\sas-path-list) + \s(?P<asn>.+)\s* + $""", re.VERBOSE, + ), + "setval": "match as-number {{ match.as_number.asn|join(', ') }}", + "result": { + "{{ route_map }}": { + "route_map": "{{ route_map }}", + "entries": { + "{{ sequence }}": { + "match": { + "as_number": { + "asn": "{{ asn.rstrip().split(', ') }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.as_number.as_path_list", + "getval": re.compile( + r""" + \s+match\sas-number + \sas-path-list\s(?P<as_path_list>.+)\s* + $""", re.VERBOSE, + ), + "setval": "match as-number as-path-list {{ match.as_number.as_path_list|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "as_number": { + "as_path_list": "{{ as_path_list.split() }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.as_path", + "getval": re.compile( + r""" + \s+match\sas-path\s(?P<as_path>.+)\s* + $""", re.VERBOSE, + ), + "setval": "match as-path {{ match.as_path|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "as_path": "{{ as_path.split() }}", + }, + }, + }, + }, + }, + }, + { + "name": "match.community.community_list", + "getval": re.compile( + r""" + \s+match\scommunity + \s(?P<community_list>.+) + (\s(?P<exact_match>exact-match))? + \s* + $""", re.VERBOSE, + ), + "setval": "match community {{ match.community.community_list|join(' ') }}{{ ' exact-match' if match.community.exact_match|d(False) else '' }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "community": { + "community_list": "{{ community_list.split() }}", + "exact_match": "{{ not not exact_match }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.evpn.route_types", + "getval": re.compile( + r""" + \s+match\sevpn + \sroute-type + \s(?P<route_types>.+)\s* + $""", re.VERBOSE, + ), + "setval": "match evpn route-type {{ match.evpn.route_types|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "evpn": { + "route_types": "{{ route_types.split() }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.extcommunity.extcommunity_list", + "getval": re.compile( + r""" + \s+match\sextcommunity + \s(?P<extcommunity_list>.+) + \s(?P<exact_match>exact-match)? + \s* + $""", re.VERBOSE, + ), + "setval": "match extcommunity {{ match.extcommunity.extcommunity_list|join(' ') }}" + "{{ ' exact-match' if match.extcommunity.exact_match|d(False) else '' }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "extcommunity": { + "extcommunity_list": "{{ extcommunity_list.split() }}", + "exact_match": "{{ not not exact_match }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.interfaces", + "getval": re.compile( + r""" + \s+match\sinterface + \s(?P<interfaces>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match interface {{ match.interfaces|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "interfaces": "{{ interfaces.split() }}", + }, + }, + }, + }, + }, + }, + { + "name": "match.ip.address.access_list", + "getval": re.compile( + r""" + \s+match\sip\saddress + \s(?P<access_list>\S+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ip address {{ match.ip.address.access_list }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ip": { + "address": { + "access_list": "{{ access_list }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.ip.address.prefix_lists", + "getval": re.compile( + r""" + \s+match\sip\saddress + \sprefix-list + \s(?P<prefix_lists>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ip address prefix-list {{ match.ip.address.prefix_lists|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ip": { + "address": { + "prefix_lists": "{{ prefix_lists.split() }}", + }, + }, + }, + }, + }, + }, + }, + }, + # match ip multicast source 192.1.2.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 209.165.201.0/27 rp-type Bidir + { + "name": "match.ip.multicast", + "getval": re.compile( + r""" + \s+match\sip\smulticast + (\ssource\s(?P<source>\S+))? + (\sgroup\s(?P<prefix>\S+))? + (\sgroup-range + (\s(?P<first>\S+))? + (\sto)? + (\s(?P<last>\S+)))? + (\srp\s(?P<rp>\S+))? + (\srp-type\s(?P<rp_type>\S+))? + \s* + $""", re.VERBOSE, + ), + "setval": _tmplt_match_ip_multicast, + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ip": { + "multicast": { + "group": { + "prefix": "{{ prefix }}", + }, + "group_range": { + "first": "{{ first }}", + "last": "{{ last }}", + }, + "rp": { + "prefix": "{{ rp }}", + "rp_type": "{{ rp_type }}", + }, + "source": "{{ source }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.ip.next_hop.prefix_lists", + "getval": re.compile( + r""" + \s+match\sip\snext-hop + \sprefix-list\s(?P<prefix_lists>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ip next-hop prefix-list {{ match.ip.next_hop.prefix_lists|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ip": { + "next_hop": { + "prefix_lists": "{{ prefix_lists.split() }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.ip.route_source.prefix_lists", + "getval": re.compile( + r""" + \s+match\sip\sroute-source + \sprefix-list\s(?P<prefix_lists>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ip route-source prefix-list {{ match.ip.route_source.prefix_lists|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ip": { + "route_source": { + "prefix_lists": "{{ prefix_lists.split() }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.ipv6.address.access_list", + "getval": re.compile( + r""" + \s+match\sipv6\saddress + \s(?P<access_list>\S+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ipv6 address {{ match.ipv6.address.access_list }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ipv6": { + "address": { + "access_list": "{{ access_list }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.ipv6.address.prefix_lists", + "getval": re.compile( + r""" + \s+match\sipv6\saddress + \sprefix-list + \s(?P<prefix_lists>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ipv6 address prefix-list {{ match.ipv6.address.prefix_lists|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ipv6": { + "address": { + "prefix_lists": "{{ prefix_lists.split() }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.ipv6.multicast", + "getval": re.compile( + r""" + \s+match\sipv6\smulticast + (\ssource\s(?P<source>\S+))? + (\sgroup\s(?P<prefix>\S+))? + (\sgroup-range + (\s(?P<first>\S+))? + (\sto)? + (\s(?P<last>\S+)))? + (\srp\s(?P<rp>\S+))? + (\srp-type\s(?P<rp_type>\S+))? + \s* + $""", re.VERBOSE, + ), + "setval": _tmplt_match_ipv6_multicast, + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ipv6": { + "multicast": { + "group": { + "prefix": "{{ prefix }}", + }, + "group_range": { + "first": "{{ first }}", + "last": "{{ last }}", + }, + "rp": { + "prefix": "{{ rp }}", + "rp_type": "{{ rp_type }}", + }, + "source": "{{ source }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.ipv6.next_hop.prefix_lists", + "getval": re.compile( + r""" + \s+match\sipv6\snext-hop + \sprefix-list\s(?P<prefix_lists>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ipv6 next-hop prefix-list {{ match.ipv6.next_hop.prefix_lists|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ipv6": { + "next_hop": { + "prefix_lists": "{{ prefix_lists.split() }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.ipv6.route_source.prefix_lists", + "getval": re.compile( + r""" + \s+match\sipv6\sroute-source + \sprefix-list\s(?P<prefix_lists>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ipv6 route-source prefix-list {{ match.ipv6.route_source.prefix_lists|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ipv6": { + "route_source": { + "prefix_lists": "{{ prefix_lists.split() }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match.mac_list", + "getval": re.compile( + r""" + \s+match\smac-list + \s(?P<mac_list>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match mac-list {{ match.mac_list|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "mac_list": "{{ mac_list.split() }}", + }, + }, + }, + }, + }, + }, + { + "name": "match.metric", + "getval": re.compile( + r""" + \s+match\smetric + \s(?P<metric>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match metric {{ match.metric|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "metric": "{{ metric.split() }}", + }, + }, + }, + }, + }, + }, + { + "name": "match.ospf_area", + "getval": re.compile( + r""" + \s+match\sospf-area + \s(?P<ospf_area>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match ospf-area {{ match.ospf_area|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "ospf_area": "{{ ospf_area.split() }}", + }, + }, + }, + }, + }, + }, + { + "name": "match.route_types", + "getval": re.compile( + r""" + \s+match\sroute-type + \s(?P<route_types>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match route-type {{ match.route_types|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "route_types": "{{ route_types.split() }}", + }, + }, + }, + }, + }, + }, + { + "name": "match.source_protocol", + "getval": re.compile( + r""" + \s+match\ssource-protocol + \s(?P<route_type>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match source-protocol {{ match.source_protocol|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "source_protocol": "{{ source_protocol.split() }}", + }, + }, + }, + }, + }, + }, + { + "name": "match.tags", + "getval": re.compile( + r""" + \s+match\stag + \s(?P<tags>.+) + \s* + $""", re.VERBOSE, + ), + "setval": "match tag {{ match.tags|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "match": { + "tags": "{{ tags.split() }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.as_path.prepend.as_number", + "getval": re.compile( + r""" + \s+set\sas-path\sprepend + \s(?P<as_number>(?!last-as).+) + \s* + $""", re.VERBOSE, + ), + "setval": "set as-path prepend {{ set.as_path.prepend.as_number|join(' ') }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "as_path": { + "prepend": { + "as_number": "{{ as_number.split() }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.as_path.prepend.last_as", + "getval": re.compile( + r""" + \s+set\sas-path\sprepend + \slast-as\s(?P<last_as>\d+) + \s* + $""", re.VERBOSE, + ), + "setval": "set as-path prepend last-as {{ set.as_path.prepend.last_as|string }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "as_path": { + "prepend": { + "last_as": "{{ last_as }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.as_path.tag", + "getval": re.compile( + r""" + \s+set\sas-path + \s(?P<tag>tag) + \s* + $""", re.VERBOSE, + ), + "setval": "set as-path tag", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "as_path": { + "tag": "{{ not not tag }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.comm_list", + "getval": re.compile( + r""" + \s+set\scomm-list + \s(?P<comm_list>\S+) + \s*delete + \s*$""", re.VERBOSE, + ), + "setval": "set comm-list {{ set.comm_list }} delete", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "comm_list": "{{ comm_list }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.community", + "getval": re.compile( + r""" + \s+set\scommunity + (\s(?P<internet>internet))? + (?P<number>(\s\d+:\d+)*) + (\s(?P<no_export>no-export))? + (\s(?P<no_advertise>no-advertise))? + (\s(?P<local_as>local-AS))? + (\s(?P<graceful_shutdown>graceful-shutdown))? + (\s(?P<additive>additive))?\s* + $""", re.VERBOSE, + ), + "setval": "set community" + "{{ ' internet' if set.community.internet|d(False) else '' }}" + "{{ ' ' + set.community.number|join(' ') if set.community.number|d(False) else '' }}" + "{{ ' no-export' if set.community.no_export|d(False) else '' }}" + "{{ ' no-advertise' if set.community.no_advertise|d(False) else '' }}" + "{{ ' local-AS' if set.community.local_as|d(False) else '' }}" + "{{ ' graceful-shutdown' if set.community.graceful_shutdown|d(False) else '' }}" + "{{ ' additive' if set.community.additive|d(False) else '' }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "community": { + "internet": "{{ not not internet }}", + "number": "{{ number.split() }}", + "no_export": "{{ not not no_export }}", + "no_advertise": "{{ not not no_advertise }}", + "local_as": "{{ not not local_as }}", + "graceful_shutdown": "{{ not not graceful_shutdown }}", + "additive": "{{ not not additive }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.dampening", + "getval": re.compile( + r""" + \s+set\sdampening + \s(?P<half_life>\d+) + \s(?P<start_reuse_route>\d+) + \s(?P<start_suppress_route>\d+) + \s(?P<max_suppress_time>\d+) + \s* + $""", re.VERBOSE, + ), + "setval": "set dampening {{ set.dampening.half_life }}" + " {{ set.dampening.start_reuse_route }}" + " {{ set.dampening.start_suppress_route }}" + " {{ set.dampening.max_suppress_time }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "dampening": { + "half_life": "{{ half_life }}", + "start_reuse_route": "{{ start_reuse_route }}", + "start_suppress_route": "{{ start_suppress_route }}", + "max_suppress_time": "{{ max_suppress_time }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.distance", + "getval": re.compile( + r""" + \s+set\sdistance + \s(?P<igp_ebgp_routes>\d+) + (\s(?P<internal_routes>\d+))? + (\s(?P<local_routes>\d+))? + \s* + $""", re.VERBOSE, + ), + "setval": "set distance {{ set.distance.igp_ebgp_routes }}" + "{{ ' ' + set.distance.internal_routes|string if set.distance.internal_routes|d(False) else '' }}" + "{{ ' ' + set.distance.local_routes|string if set.distance.internal_routes|d(False) else '' }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "distance": { + "igp_ebgp_routes": "{{ igp_ebgp_routes }}", + "internal_routes": "{{ internal_routes }}", + "local_routes": "{{ local_routes }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.evpn.gateway_ip", + "getval": re.compile( + r""" + \s+set\sevpn + \sgateway-ip + (\s(?P<ip>(?!use-nexthop)\S+))? + (\s(?P<use_nexthop>use-nexthop))? + \s* + $""", re.VERBOSE, + ), + "setval": "set evpn gateway-ip" + "{{ ' ' + set.evpn.gateway_ip.ip if set.evpn.gateway_ip.ip|d(False) else ''}}" + "{{ ' use-nexthop' if set.evpn.gateway_ip.use_nexthop|d(False) else '' }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "evpn": { + "gateway_ip": { + "ip": "{{ ip }}", + "use_nexthop": "{{ not not use_nexthop }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.extcomm_list", + "getval": re.compile( + r""" + \s+set\sextcomm-list + \s(?P<extcomm_list>\S+) + \s*delete + \s*$""", re.VERBOSE, + ), + "setval": "set extcomm-list {{ set.extcomm_list }} delete", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "extcomm_list": "{{ extcomm_list }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.forwarding_address", + "getval": re.compile( + r""" + \s+set + \s(?P<forwarding_address>forwarding-address) + \s*$""", re.VERBOSE, + ), + "setval": "set forwarding-address", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "forwarding_address": "{{ not not forwarding_address }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.null_interface", + "getval": re.compile( + r""" + \s+set\sinterface + \s(?P<interface>\S+) + \s*$""", re.VERBOSE, + ), + "setval": "set interface {{ set.null_interface }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "null_interface": "{{ interface }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.ip.address.prefix_list", + "getval": re.compile( + r""" + \s+set\sip\saddress + \sprefix-list\s(?P<prefix_list>\S+) + \s*$""", re.VERBOSE, + ), + "setval": "set ip address prefix-list {{ set.ip.address.prefix_list }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "ip": { + "address": { + "prefix_list": "{{ prefix_list }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.ip.precedence", + "getval": re.compile( + r""" + \s+set\sip + \sprecedence\s(?P<precedence>\S+) + \s*$""", re.VERBOSE, + ), + "setval": "set ip precedence {{ set.ip.precedence }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "ip": { + "precedence": "{{ precedence }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.ipv6.address.prefix_list", + "getval": re.compile( + r""" + \s+set\sipv6\saddress + \sprefix-list\s(?P<prefix_list>\S+) + \s*$""", re.VERBOSE, + ), + "setval": "set ipv6 address prefix-list {{ set.ipv6.address.prefix_list }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "ipv6": { + "address": { + "prefix_list": "{{ prefix_list }}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.ipv6.precedence", + "getval": re.compile( + r""" + \s+set\sipv6 + \sprecedence\s(?P<precedence>\S+) + \s*$""", re.VERBOSE, + ), + "setval": "set ipv6 precedence {{ set.ipv6.precedence }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "ipv6": { + "precedence": "{{ precedence }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.label_index", + "getval": re.compile( + r""" + \s+set\slabel-index + \s(?P<label_index>\d+) + \s*$""", re.VERBOSE, + ), + "setval": "set label-index {{ set.label_index }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "label_index": "{{ label_index }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.level", + "getval": re.compile( + r""" + \s+set\slevel + \s(?P<level>\S+) + \s*$""", re.VERBOSE, + ), + "setval": "set level {{ set.level }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "level": "{{ level }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.local_preference", + "getval": re.compile( + r""" + \s+set\slocal-preference + \s(?P<local_preference>\d+) + \s*$""", re.VERBOSE, + ), + "setval": "set local-preference {{ set.local_preference }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "local_preference": "{{ local_preference }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.metric", + "getval": re.compile( + r""" + \s+set\smetric + \s(?P<bandwidth>\d+) + (\s(?P<igrp_delay_metric>\d+))? + (\s(?P<igrp_reliability_metric>\d+))? + (\s(?P<igrp_effective_bandwidth_metric>\d+))? + (\s(?P<igrp_mtu>\d+))? + \s*$""", re.VERBOSE, + ), + "setval": _tmplt_set_metric, + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "metric": { + "bandwidth": "{{ bandwidth }}", + "igrp_delay_metric": "{{ igrp_delay_metric }}", + "igrp_reliability_metric": "{{ igrp_reliability_metric }}", + "igrp_effective_bandwidth_metric": "{{ igrp_effective_bandwidth_metric }}", + "igrp_mtu": "{{ igrp_mtu }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set.metric_type", + "getval": re.compile( + r""" + \s+set\smetric-type + \s(?P<metric_type>\S+) + \s*$""", re.VERBOSE, + ), + "setval": "set metric-type {{ set.metric_type }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "metric_type": "{{ metric_type }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.nssa_only", + "getval": re.compile( + r""" + \s+set + \s(?P<nssa_only>nssa-only) + \s*$""", re.VERBOSE, + ), + "setval": "set nssa-only", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "nssa_only": "{{ not not nssa_only }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.origin", + "getval": re.compile( + r""" + \s+set\sorigin + \s(?P<origin>\S+) + \s*$""", re.VERBOSE, + ), + "setval": "set origin {{ set.origin }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "origin": "{{ origin }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.path_selection", + "getval": re.compile( + r""" + \s+set\spath-selection + \s(?P<path_selection>\S+) + \sadvertise + \s*$""", re.VERBOSE, + ), + "setval": "set path-selection {{ set.path_selection }} advertise", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "path_selection": "{{ path_selection }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.tag", + "getval": re.compile( + r""" + \s+set\stag + \s(?P<tag>\d+) + \s*$""", re.VERBOSE, + ), + "setval": "set tag {{ set.tag }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "tag": "{{ tag }}", + }, + }, + }, + }, + }, + }, + { + "name": "set.weight", + "getval": re.compile( + r""" + \s+set\sweight + \s(?P<weight>\d+) + \s*$""", re.VERBOSE, + ), + "setval": "set weight {{ set.weight }}", + "result": { + "{{ route_map }}": { + "entries": { + "{{ sequence }}": { + "set": { + "weight": "{{ weight }}", + }, + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/snmp_server.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/snmp_server.py new file mode 100644 index 00000000..8615bb2c --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/snmp_server.py @@ -0,0 +1,1550 @@ +# -*- 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) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +""" +The Snmp_server parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _template_hosts(data): + cmd = "snmp-server host {0}".format(data["host"]) + if data.get("traps"): + cmd += " traps" + if data.get("informs"): + cmd += " informs" + if data.get("use_vrf"): + cmd += " use-vrf {0}".format(data["use_vrf"]) + if data.get("filter_vrf"): + cmd += " filter-vrf {0}".format(data["filter_vrf"]) + if data.get("source_interface"): + cmd += " source-interface {0}".format(data["source_interface"]) + if data.get("version"): + cmd += " version {0}".format(data["version"]) + if data.get("community"): + cmd += " " + data["community"] + elif data.get("auth"): + cmd += " auth {0}".format(data["auth"]) + elif data.get("priv"): + cmd += " priv {0}".format(data["priv"]) + if data.get("udp_port"): + cmd += " udp-port {0}".format(data["udp_port"]) + + return cmd + + +def _tmplt_users_auth(data): + cmd = "snmp-server user {0}".format(data["user"]) + + if "group" in data: + cmd += " {0}".format(data["group"]) + if "authentication" in data: + auth = data["authentication"] + if "algorithm" in auth: + cmd += " auth {0}".format(auth["algorithm"]) + if "password" in auth: + cmd += " {0}".format(auth["password"]) + priv = auth.get("priv", {}) + if priv: + cmd += " priv" + if priv.get("aes_128", False): + cmd += " aes-128" + if "privacy_password" in priv: + cmd += " {0}".format(priv["privacy_password"]) + if auth.get("localized_key", False): + cmd += " localizedkey" + elif auth.get("localizedv2_key", False): + cmd += " localizedV2key" + if "engine_id" in auth: + cmd += " engineID {0}".format(auth["engine_id"]) + + return cmd + + +def _template_communities(data): + cmd = "snmp-server community {0}".format(data["name"]) + + if "group" in data: + cmd += " group {0}".format(data["group"]) + elif "use_ipv4acl" in data: + cmd += " use-ipv4acl {0}".format(data["use_ipv4acl"]) + elif "use_ipv6acl" in data: + cmd += " use-ipv6acl {0}".format(data["use_ipv6acl"]) + elif data.get("ro", False): + cmd += " ro" + elif data.get("rw", False): + cmd += " rw" + + return cmd + + +class Snmp_serverTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Snmp_serverTemplate, self).__init__(lines=lines, tmplt=self, module=module) + + # fmt: off + PARSERS = [ + { + "name": "aaa_user.cache_timeout", + "getval": re.compile( + r""" + ^snmp-server\saaa-user + \scache-timeout\s(?P<cache_timeout>\d+) + $""", re.VERBOSE, + ), + "setval": "snmp-server aaa-user cache-timeout {{ aaa_user.cache_timeout }}", + "result": { + "aaa_user": { + "cache_timeout": "{{ cache_timeout }}", + }, + }, + }, + { + "name": "communities", + "getval": re.compile( + r""" + ^snmp-server + \scommunity\s(?P<community>\S+) + (\sgroup\s(?P<group>\S+))? + (\suse-ipv4acl\s(?P<use_ipv4acl>\S+))? + (\suse-ipv6acl\s(?P<use_ipv6acl>\S+))? + $""", re.VERBOSE, + ), + "setval": _template_communities, + "result": { + "communities": [ + { + "name": "{{ community }}", + "group": "{{ group }}", + "use_ipv4acl": "{{ use_ipv4acl }}", + "use_ipv6acl": "{{ use_ipv6acl }}", + }, + ], + }, + }, + { + "name": "contact", + "getval": re.compile( + r""" + ^snmp-server + \scontact\s(?P<contact>.+) + $""", re.VERBOSE, + ), + "setval": "snmp-server contact {{ contact }}", + "result": { + "contact": "{{ contact }}", + }, + }, + { + "name": "context", + "getval": re.compile( + r""" + ^snmp-server + \scontext\s(?P<name>\S+) + (\sinstance\s(?P<instance>\S+))? + (\svrf\s(?P<vrf>\S+))? + (\stopology\s(?P<topology>\S+))? + $""", re.VERBOSE, + ), + "setval": "snmp-server context {{ context.name }}" + "{{ ' instance ' + context.instance if context.instance is defined else '' }}" + "{{ ' topology ' + context.topology if context.topology is defined else '' }}" + "{{ ' vrf ' + context.vrf if context.vrf is defined else '' }}", + "result": { + "context": { + "name": "{{ name }}", + "instance": "{{ instance }}", + "vrf": "{{ vrf }}", + "topology": "{{ topology }}", + }, + + }, + }, + { + "name": "counter.enable", + "getval": re.compile( + r""" + ^snmp-server + \scounter + \scache\s(?P<enable>enable) + $""", re.VERBOSE, + ), + "setval": "snmp-server counter cache enable", + "result": { + "counter": { + "cache": { + "enable": "{{ True if enable is defined else None }}", + }, + }, + }, + }, + { + "name": "counter.cache.timeout", + "getval": re.compile( + r""" + ^snmp-server + \scounter + \scache\stimeout\s(?P<timeout>\d+) + $""", re.VERBOSE, + ), + "setval": "snmp-server counter cache timeout {{ counter.cache.timeout }}", + "result": { + "counter": { + "cache": { + "timeout": "{{ timeout }}", + }, + }, + }, + }, + { + "name": "drop.unknown_engine_id", + "getval": re.compile( + r""" + ^snmp-server\sdrop + \s(?P<unknown_engine_id>unknown-engine-id) + $""", re.VERBOSE, + ), + "setval": "snmp-server drop unknown-engine-id", + "result": { + "drop": { + "unknown_engine_id": "{{ not not unknown_engine_id }}", + }, + }, + }, + { + "name": "drop.unknown_user", + "getval": re.compile( + r""" + ^snmp-server\sdrop + \s(?P<unknown_user>unknown-user) + $""", re.VERBOSE, + ), + "setval": "snmp-server drop unknown-user", + "result": { + "drop": { + "unknown_user": "{{ not not unknown_user }}", + }, + }, + }, + { + "name": "traps.aaa", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps\saaa\s(?P<server_state_change>server-state-change) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps aaa" + "{{ ' server-state-change' if traps.aaa.server_state_change|d(False) else ''}}", + "result": { + "traps": { + "aaa": { + "server_state_change": "{{ not not server_state_change }}", + }, + }, + }, + }, + { + "name": "traps.bgp", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps\s(?P<enable>bgp) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps bgp", + "result": { + "traps": { + "bgp": { + "enable": "{{ not not enable }}", + }, + }, + }, + }, + { + "name": "traps.bridge.newroot", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sbridge\s(?P<newroot>newroot) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps bridge newroot", + "result": { + "traps": { + "bridge": { + "newroot": "{{ not not newroot }}", + }, + }, + }, + }, + { + "name": "traps.bridge.topologychange", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sbridge\s(?P<topologychange>topologychange) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps bridge topologychange", + "result": { + "traps": { + "bridge": { + "topologychange": "{{ not not topologychange }}", + }, + }, + }, + }, + { + "name": "traps.callhome.event_notify", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \scallhome\s(?P<event_notify>event-notify) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps callhome event-notify", + "result": { + "traps": { + "callhome": { + "event_notify": "{{ not not event_notify }}", + }, + }, + }, + }, + { + "name": "traps.callhome.smtp_send_fail", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \scallhome\s(?P<smtp_send_fail>smtp-send-fail) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps callhome smtp-send-fail", + "result": { + "traps": { + "callhome": { + "smtp_send_fail": "{{ not not smtp_send_fail }}", + }, + }, + }, + }, + { + "name": "traps.cfs.merge_failure", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \scfs\s(?P<merge_failure>merge-failure) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps cfs merge-failure", + "result": { + "traps": { + "cfs": { + "merge_failure": "{{ not not merge_failure }}", + }, + }, + }, + }, + { + "name": "traps.cfs.state_change_notif", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \scfs\s(?P<state_change_notif>state-change-notif) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps cfs state-change-notif", + "result": { + "traps": { + "cfs": { + "state_change_notif": "{{ not not state_change_notif }}", + }, + }, + }, + }, + { + "name": "traps.config.ccmCLIRunningConfigChanged", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sconfig\s(?P<ccmCLIRunningConfigChanged>ccmCLIRunningConfigChanged) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps config ccmCLIRunningConfigChanged", + "result": { + "traps": { + "config": { + "ccmCLIRunningConfigChanged": "{{ not not ccmCLIRunningConfigChanged }}", + }, + }, + }, + }, + { + "name": "traps.entity.cefcMIBEnableStatusNotification", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<cefcMIBEnableStatusNotification>cefcMIBEnableStatusNotification) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity cefcMIBEnableStatusNotification", + "result": { + "traps": { + "entity": { + "cefcMIBEnableStatusNotification": "{{ not not cefcMIBEnableStatusNotification }}", + }, + }, + }, + }, + { + "name": "traps.entity.entity_fan_status_change", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<entity_fan_status_change>entity-fan-status-change) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity entity-fan-status-change", + "result": { + "traps": { + "entity": { + "entity_fan_status_change": "{{ not not entity_fan_status_change }}", + }, + }, + }, + }, + { + "name": "traps.entity.entity_mib_change", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<entity_mib_change>entity-mib-change) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity entity-mib-change", + "result": { + "traps": { + "entity": { + "entity_mib_change": "{{ not not entity_mib_change }}", + }, + }, + }, + }, + { + "name": "traps.entity.entity_module_inserted", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<entity_module_inserted>entity-module-inserted) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity entity-module-inserted", + "result": { + "traps": { + "entity": { + "entity_module_inserted": "{{ not not entity_module_inserted }}", + }, + }, + }, + }, + { + "name": "traps.entity.entity_module_status_change", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<entity_module_status_change>entity-module-status-change) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity entity-module-status-change", + "result": { + "traps": { + "entity": { + "entity_module_status_change": "{{ not not entity_module_status_change }}", + }, + }, + }, + }, + { + "name": "traps.entity.entity_power_out_change", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<entity_power_out_change>entity-power-out-change) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity entity-power-out-change", + "result": { + "traps": { + "entity": { + "entity_power_out_change": "{{ not not entity_power_out_change }}", + }, + }, + }, + }, + { + "name": "traps.entity.entity_power_status_change", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<entity_power_status_change>entity_power_status_change) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity entity-power-status-change", + "result": { + "traps": { + "entity": { + "entity_power_status_change": "{{ not not entity_power_status_change }}", + }, + }, + }, + }, + { + "name": "traps.entity.entity_sensor", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<entity_sensor>entity-sensor) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity entity-sensor", + "result": { + "traps": { + "entity": { + "entity_sensor": "{{ not not entity_sensor }}", + }, + }, + }, + }, + { + "name": "traps.entity.entity_unrecognised_module", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sentity\s(?P<entity_unrecognised_module>entity-unrecognised-module) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps entity entity-unrecognised-module", + "result": { + "traps": { + "entity": { + "entity_unrecognised_module": "{{ not not entity_unrecognised_module }}", + }, + }, + }, + }, + { + "name": "traps.feature_control.featureOpStatusChange", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sfeature-control\s(?P<featureOpStatusChange>featureOpStatusChange) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps feature-control featureOpStatusChange", + "result": { + "traps": { + "feature_control": { + "featureOpStatusChange": "{{ not not featureOpStatusChange }}", + }, + }, + }, + }, + { + "name": "traps.feature_control.ciscoFeatOpStatusChange", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sfeature-control\s(?P<ciscoFeatOpStatusChange>ciscoFeatOpStatusChange) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps feature-control ciscoFeatOpStatusChange", + "result": { + "traps": { + "feature_control": { + "ciscoFeatOpStatusChange": "{{ not not ciscoFeatOpStatusChange }}", + }, + }, + }, + }, + { + "name": "traps.generic.coldStart", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sgeneric\s(?P<coldStart>coldStart) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps generic coldStart", + "result": { + "traps": { + "generic": { + "coldStart": "{{ not not coldStart }}", + }, + }, + }, + }, + { + "name": "traps.generic.warmStart", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sgeneric\s(?P<warmStart>warmStart) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps generic warmStart", + "result": { + "traps": { + "generic": { + "warmStart": "{{ not not warmStart }}", + }, + }, + }, + }, + { + "name": "traps.license.notify_license_expiry", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slicense\s(?P<notify_license_expiry>notify_license_expiry) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps license notify-license-expiry", + "result": { + "traps": { + "license": { + "notify_license_expiry": "{{ not not notify_license_expiry }}", + }, + }, + }, + }, + { + "name": "traps.license.notify_license_expiry_warning", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slicense\s(?P<notify_license_expiry_warning>notify-license-expiry-warning) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps license notify-license-expiry-warning", + "result": { + "traps": { + "license": { + "notify_license_expiry_warning": "{{ not not notify_license_expiry_warning }}", + }, + }, + }, + }, + { + "name": "traps.license.notify_licensefile_missing", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slicense\s(?P<notify_licensefile_missing>notify-licensefile-missing) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps license notify-licensefile-missing", + "result": { + "traps": { + "license": { + "notify_licensefile_missing": "{{ not not notify_licensefile_missing }}", + }, + }, + }, + }, + { + "name": "traps.license.notify_no_license_for_feature", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slicense\s(?P<notify_no_license_for_feature>notify-no-license-for-feature) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps license notify-no-license-for-feature", + "result": { + "traps": { + "license": { + "notify_no_license_for_feature": "{{ not not notify_no_license_for_feature }}", + }, + }, + }, + }, + { + "name": "traps.link.cErrDisableInterfaceEventRev1", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<cErrDisableInterfaceEventRev1>cErrDisableInterfaceEventRev1) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link cErrDisableInterfaceEventRev1", + "result": { + "traps": { + "link": { + "cErrDisableInterfaceEventRev1": "{{ not not cErrDisableInterfaceEventRev1 }}", + }, + }, + }, + }, + { + "name": "traps.link.cieLinkDown", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<cieLinkDown>cieLinkDown) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link cieLinkDown", + "result": { + "traps": { + "link": { + "cieLinkDown": "{{ not not cieLinkDown }}", + }, + }, + }, + }, + { + "name": "traps.link.cieLinkUp", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<cieLinkUp>cieLinkUp) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link cieLinkUp", + "result": { + "traps": { + "link": { + "cieLinkUp": "{{ not not cieLinkUp }}", + }, + }, + }, + }, + { + "name": "traps.link.cisco_xcvr_mon_status_chg", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<cisco_xcvr_mon_status_chg>cisco-xcvr-mon-status-chg) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link cisco-xcvr-mon-status-chg", + "result": { + "traps": { + "link": { + "cisco_xcvr_mon_status_chg": "{{ not not cisco_xcvr_mon_status_chg }}", + }, + }, + }, + }, + { + "name": "traps.link.cmn_mac_move_notification", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<cmn_mac_move_notification>cmn-mac-move-notification) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link cmn-mac-move-notification", + "result": { + "traps": { + "link": { + "cmn_mac_move_notification": "{{ not not cmn_mac_move_notification }}", + }, + }, + }, + }, + { + "name": "traps.link.delayed_link_state_change", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<delayed_link_state_change>delayed-link-state-change) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link delayed-link-state-change", + "result": { + "traps": { + "link": { + "delayed_link_state_change": "{{ not not delayed_link_state_change }}", + }, + }, + }, + }, + { + "name": "traps.link.extended_linkDown", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<extended_linkDown>extended-linkDown) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link extended-linkDown", + "result": { + "traps": { + "link": { + "extended_linkDown": "{{ not not extended_linkDown }}", + }, + }, + }, + }, + { + "name": "traps.link.extended_linkUp", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<extended_linkUp>extended-linkUp) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link extended-linkUp", + "result": { + "traps": { + "link": { + "extended_linkUp": "{{ not not extended_linkUp }}", + }, + }, + }, + }, + { + "name": "traps.link.linkDown", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<linkDown>linkDown) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link linkDown", + "result": { + "traps": { + "link": { + "linkDown": "{{ not not linkDown }}", + }, + }, + }, + }, + { + "name": "traps.link.linkUp", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \slink\s(?P<linkUp>linkUp) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps link linkUp", + "result": { + "traps": { + "link": { + "linkUp": "{{ not not linkUp }}", + }, + }, + }, + }, + { + "name": "traps.mmode.cseMaintModeChangeNotify", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \smmode\s(?P<cseMaintModeChangeNotify>cseMaintModeChangeNotify) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps mmode cseMaintModeChangeNotify", + "result": { + "traps": { + "mmode": { + "cseMaintModeChangeNotify": "{{ not not cseMaintModeChangeNotify }}", + }, + }, + }, + }, + { + "name": "traps.mmode.cseNormalModeChangeNotify", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \smmode\s(?P<cseNormalModeChangeNotify>cseNormalModeChangeNotify) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps mmode cseNormalModeChangeNotify", + "result": { + "traps": { + "mmode": { + "cseNormalModeChangeNotify": "{{ not not cseNormalModeChangeNotify }}", + }, + }, + }, + }, + { + "name": "traps.ospf", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps\s(?P<enable>ospf) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps ospf", + "result": { + "traps": { + "ospf": { + "enable": "{{ not not enable }}", + }, + }, + }, + }, + { + "name": "traps.ospfv3", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps\s(?P<enable>ospfv3) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps ospfv3", + "result": { + "traps": { + "ospfv3": { + "enable": "{{ not not enable }}", + }, + }, + }, + }, + { + "name": "traps.rf.redundancy_framework", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \srf\s(?P<redundancy_framework>redundancy-framework) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps rf redundancy-framework", + "result": { + "traps": { + "rf": { + "redundancy_framework": "{{ not not redundancy_framework }}", + }, + }, + }, + }, + { + "name": "traps.rmon.fallingAlarm", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \srmon\s(?P<fallingAlarm>fallingAlarm) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps rmon fallingAlarm", + "result": { + "traps": { + "rmon": { + "fallingAlarm": "{{ not not fallingAlarm }}", + }, + }, + }, + }, + { + "name": "traps.rmon.hcFallingAlarm", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \srmon\s(?P<hcFallingAlarm>hcFallingAlarm) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps rmon hcFallingAlarm", + "result": { + "traps": { + "rmon": { + "hcFallingAlarm": "{{ not not hcFallingAlarm }}", + }, + }, + }, + }, + { + "name": "traps.rmon.hcRisingAlarm", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \srmon\s(?P<hcRisingAlarm>hcRisingAlarm) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps rmon hcRisingAlarm", + "result": { + "traps": { + "rmon": { + "hcRisingAlarm": "{{ not not hcRisingAlarm }}", + }, + }, + }, + }, + { + "name": "traps.rmon.risingAlarm", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \srmon\s(?P<risingAlarm>risingAlarm) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps rmon risingAlarm", + "result": { + "traps": { + "rmon": { + "risingAlarm": "{{ not not risingAlarm }}", + }, + }, + }, + }, + { + "name": "traps.snmp.authentication", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \ssnmp\s(?P<authentication>authentication) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps snmp authentication", + "result": { + "traps": { + "snmp": { + "authentication": "{{ not not authentication }}", + }, + }, + }, + }, + { + "name": "traps.storm_control.cpscEventRev1", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sstorm-control\s(?P<cpscEventRev1>cpscEventRev1) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps storm-control cpscEventRev1", + "result": { + "traps": { + "storm_control": { + "cpscEventRev1n": "{{ not not cpscEventRev1 }}", + }, + }, + }, + }, + { + "name": "traps.storm_control.trap_rate", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sstorm-control\s(?P<trap_rate>trap-rate) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps storm-control trap-rate", + "result": { + "traps": { + "storm_control": { + "trap_rate": "{{ not not trap_rate }}", + }, + }, + }, + }, + { + "name": "traps.stpx.inconsistency", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sstpx\s(?P<inconsistency>inconsistency) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps stpx inconsistency", + "result": { + "traps": { + "stpx": { + "inconsistency": "{{ not not inconsistency }}", + }, + }, + }, + }, + { + "name": "traps.stpx.root_inconsistency", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sstpx\s(?P<root_inconsistency>root-inconsistency) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps stpx root-inconsistency", + "result": { + "traps": { + "stpx": { + "root_inconsistency": "{{ not not root_inconsistency }}", + }, + }, + }, + }, + { + "name": "traps.stpx.loop_inconsistency", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \sstpx\s(?P<loop_inconsistency>loop-inconsistency) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps stpx loop-inconsistency", + "result": { + "traps": { + "stpx": { + "loop_inconsistency": "{{ not not loop_inconsistency }}", + }, + }, + }, + }, + { + "name": "traps.syslog.message_generated", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \ssyslog\s(?P<message_generated>message-generated) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps syslog message-generated", + "result": { + "traps": { + "syslog": { + "message_generated": "{{ not not message_generated }}", + }, + }, + }, + }, + { + "name": "traps.sysmgr.cseFailSwCoreNotifyExtended", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \ssysmgr\s(?P<cseFailSwCoreNotifyExtended>cseFailSwCoreNotifyExtended) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps sysmgr cseFailSwCoreNotifyExtended", + "result": { + "traps": { + "sysmgr": { + "cseFailSwCoreNotifyExtended": "{{ not not cseFailSwCoreNotifyExtended }}", + }, + }, + }, + }, + { + "name": "traps.system.clock_change_notification", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \ssystem\s(?P<clock_change_notification>Clock-change-notification) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps system Clock-change-notification", + "result": { + "traps": { + "system": { + "clock_change_notification": "{{ not not clock_change_notification }}", + }, + }, + }, + }, + { + "name": "traps.upgrade.upgradeJobStatusNotify", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \supgrade\s(?P<upgradeJobStatusNotify>upgradeJobStatusNotify) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps upgrade upgradeJobStatusNotify", + "result": { + "traps": { + "upgrade": { + "upgradeJobStatusNotify": "{{ not not upgradeJobStatusNotify }}", + }, + }, + }, + }, + { + "name": "traps.upgrade.upgradeOpNotifyOnCompletion", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \supgrade\s(?P<upgradeOpNotifyOnCompletion>upgradeOpNotifyOnCompletion) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps upgrade upgradeOpNotifyOnCompletion", + "result": { + "traps": { + "upgrade": { + "upgradeOpNotifyOnCompletion": "{{ not not upgradeOpNotifyOnCompletion }}", + }, + }, + }, + }, + { + "name": "traps.vtp.notifs", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \svtp\s(?P<notifs>notifs) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps vtp notifs", + "result": { + "traps": { + "vtp": { + "notifs": "{{ not not notifs }}", + }, + }, + }, + }, + { + "name": "traps.vtp.vlancreate", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \svtp\s(?P<vlancreate>vlancreate) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps vtp vlancreate", + "result": { + "traps": { + "vtp": { + "vlancreate": "{{ not not vlancreate }}", + }, + }, + }, + }, + { + "name": "traps.vtp.vlandelete", + "getval": re.compile( + r""" + ^snmp-server\senable + \straps + \svtp\s(?P<vlandelete>vlandelete) + $""", re.VERBOSE, + ), + "setval": "snmp-server enable traps vtp vlandelete", + "result": { + "traps": { + "vtp": { + "vlandelete": "{{ not not vlandelete }}", + }, + }, + }, + }, + + { + "name": "engine_id.local", + "getval": re.compile( + r""" + ^snmp-server\sengineID + \slocal\s(?P<local>\S+) + $""", re.VERBOSE, + ), + "setval": "snmp-server engineID local {{ engine_id.local }}", + "result": { + "engine_id": { + "local": "{{ local }}", + }, + }, + }, + { + "name": "global_enforce_priv", + "getval": re.compile( + r""" + ^snmp-server + \s(?P<global_enforce_priv>globalEnforcePriv) + $""", re.VERBOSE, + ), + "setval": "snmp-server globalEnforcePriv", + "result": { + "global_enforce_priv": "{{ not not global_enforce_priv }}", + }, + }, + { + "name": "hosts", + "getval": re.compile( + r""" + ^snmp-server + \shost\s(?P<host>\S+) + (\s((?P<traps>traps)|(?P<informs>informs)|(use-vrf\s(?P<use_vrf>\S+)|(filter-vrf\s(?P<filter_vrf>\S+))|(source-interface\s(?P<source_interface>\S+))))) + (\sversion\s(?P<version>\S+))? + (\s((auth\s(?P<auth>\S+))|(priv\s(?P<priv>\S+))|((?P<community>\S+))))? + (\sudp-port\s(?P<udp_port>\S+))? + $""", re.VERBOSE, + ), + "setval": _template_hosts, + "result": { + "hosts": [ + { + "host": "{{ host }}", + "community": "{{ community }}", + "filter_vrf": "{{ filter_vrf }}", + "informs": "{{ not not informs }}", + "source_interface": "{{ source_interface }}", + "traps": "{{ not not traps }}", + "use_vrf": "{{ use_vrf }}", + "version": "{{ version }}", + "udp_port": "{{ udp_port }}", + "auth": "{{ auth }}", + "priv": "{{ priv }}", + }, + ], + }, + }, + { + "name": "location", + "getval": re.compile( + r""" + ^snmp-server + \slocation\s(?P<location>.+) + $""", re.VERBOSE, + ), + "setval": "snmp-server location {{ location }}", + "result": { + "location": "{{ location }}", + }, + }, + { + "name": "mib.community_map", + "getval": re.compile( + r""" + ^snmp-server + \smib + \scommunity-map\s(?P<community>\S+) + \scontext\s(?P<context>\S+) + $""", re.VERBOSE, + ), + "setval": "snmp-server mib community-map {{ mib.community_map.community }} context {{ mib.community_map.context }}", + "result": { + "mib": { + "community_map": { + "community": "{{ community }}", + "context": "{{ context }}", + + }, + }, + }, + }, + { + "name": "packetsize", + "getval": re.compile( + r""" + ^snmp-server + \spacketsize\s(?P<packetsize>\d+) + $""", re.VERBOSE, + ), + "setval": "snmp-server packetsize {{ packetsize }}", + "result": { + "packetsize": "{{ packetsize }}", + }, + }, + { + "name": "protocol.enable", + "getval": re.compile( + r""" + ^snmp-server + \sprotocol\s(?P<enable>enable) + $""", re.VERBOSE, + ), + "setval": "snmp-server protocol enable", + "result": { + "protocol": { + "enable": "{{ not not enable }}", + }, + }, + }, + { + "name": "source_interface.informs", + "getval": re.compile( + r""" + ^snmp-server + \ssource-interface\sinforms\s(?P<informs>\S+) + $""", re.VERBOSE, + ), + "setval": "snmp-server source-interface informs {{ source_interface.informs }}", + "result": { + "source_interface": { + "informs": "{{ informs }}", + }, + }, + }, + { + "name": "source_interface.traps", + "getval": re.compile( + r""" + ^snmp-server + \ssource-interface\straps\s(?P<traps>\S+) + $""", re.VERBOSE, + ), + "setval": "snmp-server source-interface traps {{ source_interface.traps }}", + "result": { + "source_interface": { + "traps": "{{ traps }}", + }, + }, + }, + { + "name": "system_shutdown", + "getval": re.compile( + r""" + ^snmp-server + \s(?P<system_shutdown>system-shutdown) + $""", re.VERBOSE, + ), + "setval": "snmp-server system-shutdown", + "result": { + "system_shutdown": "{{ not not system_shutdown }}", + }, + }, + { + "name": "tcp_session", + "getval": re.compile( + r""" + ^snmp-server + \s(?P<tcp_session>tcp-session) + (\s(?P<auth>auth))? + $""", re.VERBOSE, + ), + "setval": "snmp-server tcp-session" + "{{ ' auth' if tcp_session.auth|d(False) else '' }}", + "result": { + "tcp_session": { + "enable": "{{ True if tcp_session is defined and auth is not defined else None }}", + "auth": "{{ not not auth }}", + }, + }, + }, + { + "name": "users.auth", + "getval": re.compile( + r""" + ^snmp-server + \suser\s(?P<user>\S+) + (\s(?P<group>[^auth]\S+))? + (\sauth\s(?P<algorithm>md5|sha|sha-256)\s(?P<password>\S+))? + (\spriv(\s(?P<aes_128>aes-128))?\s(?P<privacy_password>\S+))? + (\s(?P<localized_key>localizedkey))? + (\s(?P<localizedv2_key>localizedV2key))? + (\sengineID\s(?P<engine_id>\S+))? + $""", re.VERBOSE, + ), + "setval": _tmplt_users_auth, + "result": { + "users": { + "auth": [ + { + "user": "{{ user }}", + "group": "{{ group }}", + "authentication": { + "algorithm": "{{ algorithm }}", + "password": "'{{ password }}'", + "engine_id": "'{{ engine_id }}'", + "localized_key": "{{ not not localized_key }}", + "localizedv2_key": "{{ not not localizedv2_key }}", + "priv": { + "privacy_password": "'{{ privacy_password }}'", + "aes_128": "{{ not not aes_128 }}", + }, + }, + + }, + ], + }, + }, + }, + { + "name": "users.use_acls", + "getval": re.compile( + r""" + ^snmp-server + \suser\s(?P<user>\S+) + (\suse-ipv4acl\s(?P<ipv4>\S+))? + (\suse-ipv6acl\s(?P<ipv6>\S+))? + $""", re.VERBOSE, + ), + "setval": "snmp-server user {{ user }}" + "{{ (' use-ipv4acl ' + ipv4) if ipv4 is defined else '' }}" + "{{ (' use-ipv6acl ' + ipv6) if ipv6 is defined else '' }}", + "result": { + "users": { + "use_acls": [ + { + "user": "{{ user }}", + "ipv4": "{{ ipv4 }}", + "ipv6": "{{ ipv6 }}", + }, + ], + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/telemetry.py new file mode 100644 index 00000000..aa540ade --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/telemetry.py @@ -0,0 +1,264 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The nxos telemetry utility library +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from copy import deepcopy + + +def get_module_params_subsection(module_params, tms_config, resource_key=None): + """ + Helper method to get a specific module_params subsection + """ + mp = {} + if tms_config == "TMS_GLOBAL": + relevant_keys = [ + "certificate", + "compression", + "source_interface", + "vrf", + ] + for key in relevant_keys: + mp[key] = module_params[key] + + if tms_config == "TMS_DESTGROUP": + mp["destination_groups"] = [] + for destgrp in module_params["destination_groups"]: + if destgrp["id"] == resource_key: + mp["destination_groups"].append(destgrp) + + if tms_config == "TMS_SENSORGROUP": + mp["sensor_groups"] = [] + for sensor in module_params["sensor_groups"]: + if sensor["id"] == resource_key: + mp["sensor_groups"].append(sensor) + + if tms_config == "TMS_SUBSCRIPTION": + mp["subscriptions"] = [] + for sensor in module_params["subscriptions"]: + if sensor["id"] == resource_key: + mp["subscriptions"].append(sensor) + + return mp + + +def valiate_input(playvals, type, module): + """ + Helper method to validate playbook values for destination groups + """ + if type == "destination_groups": + if not playvals.get("id"): + msg = "Invalid playbook value: {0}.".format(playvals) + msg += " Parameter <id> under <destination_groups> is required" + module.fail_json(msg=msg) + if playvals.get("destination") and not isinstance(playvals["destination"], dict): + msg = "Invalid playbook value: {0}.".format(playvals) + msg += " Parameter <destination> under <destination_groups> must be a dict" + module.fail_json(msg=msg) + if not playvals.get("destination") and len(playvals) > 1: + msg = "Invalid playbook value: {0}.".format(playvals) + msg += " Playbook entry contains unrecongnized parameters." + msg += ( + " Make sure <destination> keys under <destination_groups> are specified as follows:" + ) + msg += " destination: {ip: <ip>, port: <port>, protocol: <prot>, encoding: <enc>}}" + module.fail_json(msg=msg) + + if type == "sensor_groups": + if not playvals.get("id"): + msg = "Invalid playbook value: {0}.".format(playvals) + msg += " Parameter <id> under <sensor_groups> is required" + module.fail_json(msg=msg) + if playvals.get("path") and "name" not in playvals["path"].keys(): + msg = "Invalid playbook value: {0}.".format(playvals) + msg += " Parameter <path> under <sensor_groups> requires <name> key" + module.fail_json(msg=msg) + + +def get_instance_data(key, cr_key, cr, existing_key): + """ + Helper method to get instance data used to populate list structure in config + fact dictionary + """ + data = {} + if existing_key is None: + instance = None + else: + instance = cr._ref[cr_key]["existing"][existing_key] + + patterns = { + "destination_groups": r"destination-group (\S+)", + "sensor_groups": r"sensor-group (\S+)", + "subscriptions": r"subscription (\S+)", + } + if key in patterns.keys(): + m = re.search(patterns[key], cr._ref["_resource_key"]) + instance_key = m.group(1) + data = {"id": instance_key, cr_key: instance} + + # Remove None values + data = dict((k, v) for k, v in data.items() if v is not None) + return data + + +def cr_key_lookup(key, mo): + """ + Helper method to get instance key value for Managed Object (mo) + """ + cr_keys = [key] + if key == "destination_groups" and mo == "TMS_DESTGROUP": + cr_keys = ["destination"] + elif key == "sensor_groups" and mo == "TMS_SENSORGROUP": + cr_keys = ["data_source", "path"] + elif key == "subscriptions" and mo == "TMS_SUBSCRIPTION": + cr_keys = ["destination_group", "sensor_group"] + + return cr_keys + + +def normalize_data(cmd_ref): + """Normalize playbook values and get_existing data""" + + playval = cmd_ref._ref.get("destination").get("playval") + existing = cmd_ref._ref.get("destination").get("existing") + + dest_props = ["protocol", "encoding"] + if playval: + for prop in dest_props: + for key in playval.keys(): + playval[key][prop] = playval[key][prop].lower() + if existing: + for key in existing.keys(): + for prop in dest_props: + existing[key][prop] = existing[key][prop].lower() + + +def remove_duplicate_context(cmds): + """Helper method to remove duplicate telemetry context commands""" + if not cmds: + return cmds + feature_indices = [i for i, x in enumerate(cmds) if x == "feature telemetry"] + telemetry_indices = [i for i, x in enumerate(cmds) if x == "telemetry"] + if len(feature_indices) == 1 and len(telemetry_indices) == 1: + return cmds + if len(feature_indices) == 1 and not telemetry_indices: + return cmds + if len(telemetry_indices) == 1 and not feature_indices: + return cmds + if feature_indices and feature_indices[-1] > 1: + cmds.pop(feature_indices[-1]) + return remove_duplicate_context(cmds) + if telemetry_indices and telemetry_indices[-1] > 1: + cmds.pop(telemetry_indices[-1]) + return remove_duplicate_context(cmds) + + +def get_setval_path(module_or_path_data): + """Build setval for path parameter based on playbook inputs + Full Command: + - path {name} depth {depth} query-condition {query_condition} filter-condition {filter_condition} + Required: + - path {name} + Optional: + - depth {depth} + - query-condition {query_condition}, + - filter-condition {filter_condition} + """ + if isinstance(module_or_path_data, dict): + path = module_or_path_data + else: + path = module_or_path_data.params["config"]["sensor_groups"][0].get("path") + if path is None: + return path + + setval = "path {name}" + if "depth" in path.keys(): + if path.get("depth") != "None": + setval = setval + " depth {depth}" + if "query_condition" in path.keys(): + if path.get("query_condition") != "None": + setval = setval + " query-condition {query_condition}" + if "filter_condition" in path.keys(): + if path.get("filter_condition") != "None": + setval = setval + " filter-condition {filter_condition}" + + return setval + + +def remove_duplicate_commands(commands_list): + # Remove any duplicate commands. + # pylint: disable=unnecessary-lambda + return sorted(set(commands_list), key=lambda x: commands_list.index(x)) + + +def massage_data(have_or_want): + # Massage non global into a data structure that is indexed by id and + # normalized for destination_groups, sensor_groups and subscriptions. + data = deepcopy(have_or_want) + massaged = {} + massaged["destination_groups"] = {} + massaged["sensor_groups"] = {} + massaged["subscriptions"] = {} + from pprint import pprint + + for subgroup in ["destination_groups", "sensor_groups", "subscriptions"]: + for item in data.get(subgroup, []): + id = str(item.get("id")) + if id not in massaged[subgroup].keys(): + massaged[subgroup][id] = [] + item.pop("id") + if not item: + item = None + else: + if item.get("destination"): + if item.get("destination").get("port"): + item["destination"]["port"] = str(item["destination"]["port"]) + if item.get("destination").get("protocol"): + item["destination"]["protocol"] = item["destination"]["protocol"].lower() + if item.get("destination").get("encoding"): + item["destination"]["encoding"] = item["destination"]["encoding"].lower() + if item.get("path"): + for key in [ + "filter_condition", + "query_condition", + "depth", + ]: + if item.get("path").get(key) == "None": + del item["path"][key] + if item.get("path").get("depth") is not None: + item["path"]["depth"] = str(item["path"]["depth"]) + if item.get("destination_group"): + item["destination_group"] = str(item["destination_group"]) + if item.get("sensor_group"): + if item.get("sensor_group").get("id"): + item["sensor_group"]["id"] = str(item["sensor_group"]["id"]) + if item.get("sensor_group").get("sample_interval"): + item["sensor_group"]["sample_interval"] = str( + item["sensor_group"]["sample_interval"], + ) + if item.get("destination_group") and item.get("sensor_group"): + item_copy = deepcopy(item) + del item_copy["sensor_group"] + del item["destination_group"] + massaged[subgroup][id].append(item_copy) + massaged[subgroup][id].append(item) + continue + if item.get("path") and item.get("data_source"): + item_copy = deepcopy(item) + del item_copy["data_source"] + del item["path"] + massaged[subgroup][id].append(item_copy) + massaged[subgroup][id].append(item) + continue + massaged[subgroup][id].append(item) + return massaged diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/utils.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/utils.py new file mode 100644 index 00000000..01468edd --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/utils.py @@ -0,0 +1,214 @@ +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type +import socket + +from functools import total_ordering +from itertools import count, groupby + +from ansible.module_utils.six import iteritems + + +LOGGING_SEVMAP = { + 0: "emergency", + 1: "alert", + 2: "critical", + 3: "error", + 4: "warning", + 5: "notification", + 6: "informational", + 7: "debugging", +} + + +def search_obj_in_list(name, lst, identifier): + for o in lst: + if o[identifier] == name: + return o + return None + + +def flatten_dict(x): + result = {} + if not isinstance(x, dict): + return result + + for key, value in iteritems(x): + if isinstance(value, dict): + result.update(flatten_dict(value)) + else: + result[key] = value + + return result + + +def validate_ipv4_addr(address): + address = address.split("/")[0] + try: + socket.inet_aton(address) + except socket.error: + return False + return address.count(".") == 3 + + +def validate_ipv6_addr(address): + address = address.split("/")[0] + try: + socket.inet_pton(socket.AF_INET6, address) + except socket.error: + return False + return True + + +def normalize_interface(name): + """Return the normalized interface name""" + if not name: + return + + def _get_number(name): + digits = "" + for char in name: + if char.isdigit() or char in "/.": + digits += char + return digits + + if name.lower().startswith("et"): + if_type = "Ethernet" + elif name.lower().startswith("vl"): + if_type = "Vlan" + elif name.lower().startswith("lo"): + if_type = "loopback" + elif name.lower().startswith("po"): + if_type = "port-channel" + elif name.lower().startswith("nv"): + if_type = "nve" + else: + if_type = None + + number_list = name.split(" ") + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + + +def get_interface_type(interface): + """Gets the type of interface""" + if interface.upper().startswith("ET"): + return "ethernet" + elif interface.upper().startswith("VL"): + return "svi" + elif interface.upper().startswith("LO"): + return "loopback" + elif interface.upper().startswith("MG"): + return "management" + elif interface.upper().startswith("MA"): + return "management" + elif interface.upper().startswith("PO"): + return "portchannel" + elif interface.upper().startswith("NV"): + return "nve" + else: + return "unknown" + + +def remove_rsvd_interfaces(interfaces): + """Exclude reserved interfaces from user management""" + if not interfaces: + return [] + return [i for i in interfaces if get_interface_type(i["name"]) != "management"] + + +def vlan_range_to_list(vlans): + result = [] + if vlans: + for part in vlans.split(","): + if part == "none": + break + if "-" in part: + a, b = part.split("-") + a, b = int(a), int(b) + result.extend(range(a, b + 1)) + else: + a = int(part) + result.append(a) + return numerical_sort(result) + return result + + +def numerical_sort(string_int_list): + """Sorts list of integers that are digits in numerical order.""" + + as_int_list = [] + + for vlan in string_int_list: + as_int_list.append(int(vlan)) + as_int_list.sort() + return as_int_list + + +def get_logging_sevmap(invert=False): + x = LOGGING_SEVMAP + if invert: + # cannot use dict comprehension yet + # since we still test with Python 2.6 + x = dict(map(reversed, iteritems(x))) + return x + + +def get_ranges(data): + """ + Returns a generator object that yields lists of + consequtive integers from a list of integers. + """ + for _k, group in groupby(data, lambda t, c=count(): int(t) - next(c)): + yield list(group) + + +def vlan_list_to_range(cmd): + """ + Converts a comma separated list of vlan IDs + into ranges. + """ + ranges = [] + for v in get_ranges(cmd): + ranges.append("-".join(map(str, (v[0], v[-1])[: len(v)]))) + return ",".join(ranges) + + +@total_ordering +class Version: + """Simple class to compare arbitrary versions""" + + def __init__(self, version_string): + self.components = version_string.split(".") + + def __eq__(self, other): + other = _coerce(other) + if not isinstance(other, Version): + return NotImplemented + + return self.components == other.components + + def __lt__(self, other): + other = _coerce(other) + if not isinstance(other, Version): + return NotImplemented + + return self.components < other.components + + +def _coerce(other): + if isinstance(other, str): + other = Version(other) + if isinstance(other, (int, float)): + other = Version(str(other)) + return other diff --git a/ansible_collections/cisco/nxos/plugins/modules/__init__.py b/ansible_collections/cisco/nxos/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py new file mode 100644 index 00000000..9103f931 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_aaa_server +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages AAA server global configuration. +description: +- Manages AAA server global configuration +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +- The server_type parameter is always required. +- If encrypt_type is not supplied, the global AAA server key will be stored as encrypted + (type 7). +- Changes to the global AAA server key with encrypt_type=0 are not idempotent. +- state=default will set the supplied parameters to their default values. The parameters + that you want to default must also be set to default. If global_key=default, the + global key will be removed. +options: + server_type: + description: + - The server type is either radius or tacacs. + required: true + choices: + - radius + - tacacs + type: str + global_key: + description: + - Global AAA shared secret or keyword 'default'. + type: str + encrypt_type: + description: + - The state of encryption applied to the entered global key. O clear text, 7 encrypted. + Type-6 encryption is not supported. + choices: + - '0' + - '7' + type: str + deadtime: + description: + - Duration for which a non-reachable AAA server is skipped, in minutes or keyword + 'default. Range is 1-1440. Device default is 0. + type: str + server_timeout: + description: + - Global AAA server timeout period, in seconds or keyword 'default. Range is 1-60. + Device default is 5. + type: str + directed_request: + description: + - Enables direct authentication requests to AAA server or keyword 'default' Device + default is disabled. + choices: + - enabled + - disabled + - default + type: str + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - default + type: str +""" + +EXAMPLES = """ +# Radius Server Basic settings +- name: Radius Server Basic settings + cisco.nxos.nxos_aaa_server: + server_type: radius + server_timeout: 9 + deadtime: 20 + directed_request: enabled + +# Tacacs Server Basic settings +- name: Tacacs Server Basic settings + cisco.nxos.nxos_aaa_server: + server_type: tacacs + server_timeout: 8 + deadtime: 19 + directed_request: disabled + +# Setting Global Key +- name: AAA Server Global Key + cisco.nxos.nxos_aaa_server: + server_type: radius + global_key: test_key +""" + +RETURN = """ +commands: + description: command sent to the device + returned: always + type: list + sample: ["radius-server deadtime 22", "radius-server timeout 11", + "radius-server directed-request"] +""" +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +PARAM_TO_DEFAULT_KEYMAP = { + "server_timeout": "5", + "deadtime": "0", + "directed_request": "disabled", +} + + +def execute_show_command(command, module): + command = {"command": command, "output": "text"} + + return run_commands(module, command) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_aaa_server_info(server_type, module): + aaa_server_info = {} + server_command = "show {0}-server".format(server_type) + request_command = "show {0}-server directed-request".format(server_type) + global_key_command = "show run | sec {0}".format(server_type) + aaa_regex = r".*{0}-server\skey\s\d\s+(?P<key>\S+).*".format(server_type) + + server_body = execute_show_command(server_command, module)[0] + + split_server = server_body.splitlines() + + for line in split_server: + if line.startswith("timeout"): + aaa_server_info["server_timeout"] = line.split(":")[1] + + elif line.startswith("deadtime"): + aaa_server_info["deadtime"] = line.split(":")[1] + + request_body = execute_show_command(request_command, module)[0] + + if bool(request_body): + aaa_server_info["directed_request"] = request_body.replace("\n", "") + else: + aaa_server_info["directed_request"] = "disabled" + + key_body = execute_show_command(global_key_command, module)[0] + + try: + match_global_key = re.match(aaa_regex, key_body, re.DOTALL) + group_key = match_global_key.groupdict() + aaa_server_info["global_key"] = group_key["key"].replace('"', "") + except (AttributeError, TypeError): + aaa_server_info["global_key"] = None + + return aaa_server_info + + +def config_aaa_server(params, server_type): + cmds = [] + + deadtime = params.get("deadtime") + server_timeout = params.get("server_timeout") + directed_request = params.get("directed_request") + encrypt_type = params.get("encrypt_type", "7") + global_key = params.get("global_key") + + if deadtime is not None: + cmds.append("{0}-server deadtime {1}".format(server_type, deadtime)) + + if server_timeout is not None: + cmds.append("{0}-server timeout {1}".format(server_type, server_timeout)) + + if directed_request is not None: + if directed_request == "enabled": + cmds.append("{0}-server directed-request".format(server_type)) + elif directed_request == "disabled": + cmds.append("no {0}-server directed-request".format(server_type)) + + if global_key is not None: + cmds.append("{0}-server key {1} {2}".format(server_type, encrypt_type, global_key)) + + return cmds + + +def default_aaa_server(existing, params, server_type): + cmds = [] + + deadtime = params.get("deadtime") + server_timeout = params.get("server_timeout") + directed_request = params.get("directed_request") + global_key = params.get("global_key") + existing_key = existing.get("global_key") + + if deadtime is not None and existing.get("deadtime") != PARAM_TO_DEFAULT_KEYMAP["deadtime"]: + cmds.append("no {0}-server deadtime 1".format(server_type)) + + if ( + server_timeout is not None + and existing.get("server_timeout") != PARAM_TO_DEFAULT_KEYMAP["server_timeout"] + ): + cmds.append("no {0}-server timeout 1".format(server_type)) + + if ( + directed_request is not None + and existing.get("directed_request") != PARAM_TO_DEFAULT_KEYMAP["directed_request"] + ): + cmds.append("no {0}-server directed-request".format(server_type)) + + if global_key is not None and existing_key is not None: + cmds.append("no {0}-server key 7 {1}".format(server_type, existing_key)) + + return cmds + + +def main(): + argument_spec = dict( + server_type=dict(type="str", choices=["radius", "tacacs"], required=True), + global_key=dict(type="str", no_log=True), + encrypt_type=dict(type="str", choices=["0", "7"]), + deadtime=dict(type="str"), + server_timeout=dict(type="str"), + directed_request=dict(type="str", choices=["enabled", "disabled", "default"]), + state=dict(choices=["default", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + server_type = module.params["server_type"] + global_key = module.params["global_key"] + encrypt_type = module.params["encrypt_type"] + deadtime = module.params["deadtime"] + server_timeout = module.params["server_timeout"] + directed_request = module.params["directed_request"] + state = module.params["state"] + + if encrypt_type and not global_key: + module.fail_json(msg="encrypt_type must be used with global_key.") + + args = dict( + server_type=server_type, + global_key=global_key, + encrypt_type=encrypt_type, + deadtime=deadtime, + server_timeout=server_timeout, + directed_request=directed_request, + ) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + + existing = get_aaa_server_info(server_type, module) + + commands = [] + if state == "present": + if deadtime: + try: + if int(deadtime) < 0 or int(deadtime) > 1440: + raise ValueError + except ValueError: + module.fail_json(msg="deadtime must be an integer between 0 and 1440") + + if server_timeout: + try: + if int(server_timeout) < 1 or int(server_timeout) > 60: + raise ValueError + except ValueError: + module.fail_json(msg="server_timeout must be an integer between 1 and 60") + + delta = dict(set(proposed.items()).difference(existing.items())) + if delta: + command = config_aaa_server(delta, server_type) + if command: + commands.append(command) + + elif state == "default": + for key, value in proposed.items(): + if key != "server_type" and value != "default": + module.fail_json(msg='Parameters must be set to "default"' "when state=default") + command = default_aaa_server(existing, proposed, server_type) + if command: + commands.append(command) + + cmds = flatten_list(commands) + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + if "configure" in cmds: + cmds.pop(0) + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py new file mode 100644 index 00000000..d2f84f76 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py @@ -0,0 +1,370 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_aaa_server_host +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages AAA server host-specific configuration. +description: +- Manages AAA server host-specific configuration. +version_added: 1.0.0 +author: Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +- Changes to the host key (shared secret) are not idempotent for type 0. +- If C(state=absent) removes the whole host configuration. +options: + server_type: + description: + - The server type is either radius or tacacs. + required: true + choices: + - radius + - tacacs + type: str + address: + description: + - Address or name of the radius or tacacs host. + required: true + type: str + key: + description: + - Shared secret for the specified host or keyword 'default'. + type: str + encrypt_type: + description: + - The state of encryption applied to the entered key. O for clear text, 7 for + encrypted. Type-6 encryption is not supported. + choices: + - '0' + - '7' + type: str + host_timeout: + description: + - Timeout period for specified host, in seconds or keyword 'default. Range is + 1-60. + type: str + auth_port: + description: + - Alternate UDP port for RADIUS authentication or keyword 'default'. + type: str + acct_port: + description: + - Alternate UDP port for RADIUS accounting or keyword 'default'. + type: str + tacacs_port: + description: + - Alternate TCP port TACACS Server or keyword 'default'. + type: str + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - absent + type: str +""" +EXAMPLES = """ +# Radius Server Host Basic settings +- name: Radius Server Host Basic settings + cisco.nxos.nxos_aaa_server_host: + state: present + server_type: radius + address: 1.2.3.4 + acct_port: 2084 + host_timeout: 10 + +# Radius Server Host Key Configuration +- name: Radius Server Host Key Configuration + cisco.nxos.nxos_aaa_server_host: + state: present + server_type: radius + address: 1.2.3.4 + key: hello + encrypt_type: 7 + +# TACACS Server Host Configuration +- name: Tacacs Server Host Configuration + cisco.nxos.nxos_aaa_server_host: + state: present + server_type: tacacs + tacacs_port: 89 + host_timeout: 10 + address: 5.6.7.8 + +""" + +RETURN = """ +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"address": "1.2.3.4", "auth_port": "2084", + "host_timeout": "10", "server_type": "radius"} +existing: + description: + - k/v pairs of existing configuration + returned: always + type: dict + sample: {} +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: {"address": "1.2.3.4", "auth_port": "2084", + "host_timeout": "10", "server_type": "radius"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["radius-server host 1.2.3.4 auth-port 2084 timeout 10"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + load_config, + run_commands, +) + + +def execute_show_command(command, module): + device_info = get_capabilities(module) + network_api = device_info.get("network_api", "nxapi") + + if network_api == "cliconf": + cmds = [command] + body = run_commands(module, cmds) + elif network_api == "nxapi": + cmds = {"command": command, "output": "text"} + body = run_commands(module, cmds) + + return body + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_aaa_host_info(module, server_type, address): + aaa_host_info = {} + command = "show run | inc {0}-server.host.{1}".format(server_type, address) + + body = execute_show_command(command, module)[0] + if body: + try: + if "radius" in body: + pattern = ( + r"\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+auth-port (\d+))?" + r"(?:\s+acct-port (\d+))?(?:\s+authentication)?" + r"(?:\s+accounting)?(?:\s+timeout (\d+))?" + ) + match = re.search(pattern, body) + aaa_host_info["key"] = match.group(1) + if aaa_host_info["key"]: + aaa_host_info["key"] = aaa_host_info["key"].replace('"', "") + aaa_host_info["encrypt_type"] = "7" + aaa_host_info["auth_port"] = match.group(2) + aaa_host_info["acct_port"] = match.group(3) + aaa_host_info["host_timeout"] = match.group(4) + elif "tacacs" in body: + pattern = ( + r"\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+port (\d+))?(?:\s+timeout (\d+))?" + ) + match = re.search(pattern, body) + aaa_host_info["key"] = match.group(1) + if aaa_host_info["key"]: + aaa_host_info["key"] = aaa_host_info["key"].replace('"', "") + aaa_host_info["encrypt_type"] = "7" + aaa_host_info["tacacs_port"] = match.group(2) + aaa_host_info["host_timeout"] = match.group(3) + + aaa_host_info["server_type"] = server_type + aaa_host_info["address"] = address + except TypeError: + return {} + else: + return {} + + return aaa_host_info + + +def config_aaa_host(server_type, address, params, existing): + cmds = [] + cmd_str = "{0}-server host {1}".format(server_type, address) + cmd_no_str = "no " + cmd_str + + key = params.get("key") + enc_type = params.get("encrypt_type", "") + + defval = False + nondef = False + + if key: + if key != "default": + cmds.append(cmd_str + " key {0} {1}".format(enc_type, key)) + else: + cmds.append(cmd_no_str + " key 7 {0}".format(existing.get("key"))) + + locdict = { + "auth_port": "auth-port", + "acct_port": "acct-port", + "tacacs_port": "port", + "host_timeout": "timeout", + } + + # platform CLI needs the keywords in the following order + for key in ["auth_port", "acct_port", "tacacs_port", "host_timeout"]: + item = params.get(key) + if item: + if item != "default": + cmd_str += " {0} {1}".format(locdict.get(key), item) + nondef = True + else: + cmd_no_str += " {0} 1".format(locdict.get(key)) + defval = True + if defval: + cmds.append(cmd_no_str) + if nondef or not existing: + cmds.append(cmd_str) + + return cmds + + +def main(): + argument_spec = dict( + server_type=dict(choices=["radius", "tacacs"], required=True), + address=dict(type="str", required=True), + key=dict(type="str", no_log=False), + encrypt_type=dict(type="str", choices=["0", "7"]), + host_timeout=dict(type="str"), + auth_port=dict(type="str"), + acct_port=dict(type="str"), + tacacs_port=dict(type="str"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + server_type = module.params["server_type"] + address = module.params["address"] + key = module.params["key"] + encrypt_type = module.params["encrypt_type"] + host_timeout = module.params["host_timeout"] + auth_port = module.params["auth_port"] + acct_port = module.params["acct_port"] + tacacs_port = module.params["tacacs_port"] + state = module.params["state"] + + args = dict( + server_type=server_type, + address=address, + key=key, + encrypt_type=encrypt_type, + host_timeout=host_timeout, + auth_port=auth_port, + acct_port=acct_port, + tacacs_port=tacacs_port, + ) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + changed = False + + if encrypt_type and not key: + module.fail_json(msg="encrypt_type must be used with key") + + if tacacs_port and server_type != "tacacs": + module.fail_json(msg="tacacs_port can only be used with server_type=tacacs") + + if (auth_port or acct_port) and server_type != "radius": + module.fail_json(msg="auth_port and acct_port can only be used" "when server_type=radius") + + existing = get_aaa_host_info(module, server_type, address) + end_state = existing + + commands = [] + delta = {} + if state == "present": + if not existing: + delta = proposed + else: + for key, value in proposed.items(): + if key == "encrypt_type": + delta[key] = value + if value != existing.get(key): + if value != "default" or existing.get(key): + delta[key] = value + + command = config_aaa_host(server_type, address, delta, existing) + if command: + commands.append(command) + + elif state == "absent": + intersect = dict(set(proposed.items()).intersection(existing.items())) + if intersect.get("address") and intersect.get("server_type"): + command = "no {0}-server host {1}".format( + intersect.get("server_type"), + intersect.get("address"), + ) + commands.append(command) + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + changed = True + load_config(module, cmds) + end_state = get_aaa_host_info(module, server_type, address) + + results = {} + results["proposed"] = proposed + results["existing"] = existing + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + results["end_state"] = end_state + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py new file mode 100644 index 00000000..e61746ff --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py @@ -0,0 +1,440 @@ +#!/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 nxos_acl_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_acl_interfaces +short_description: ACL interfaces resource module +description: Add and remove Access Control Lists on interfaces in NX-OS platform +version_added: 1.0.0 +author: Adharsh Srivats Rangarajan (@adharshsrivatsr) +notes: +- Tested against NX-OS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^interface'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of interfaces to be configured with ACLs + type: list + elements: dict + suboptions: + name: + description: Name of the interface + type: str + required: true + access_groups: + description: List of address family indicators with ACLs to be configured + on the interface + type: list + elements: dict + suboptions: + afi: + description: Address Family Indicator of the ACLs to be configured + type: str + required: true + choices: + - ipv4 + - ipv6 + acls: + description: List of Access Control Lists for the interface + type: list + elements: dict + suboptions: + name: + description: Name of the ACL to be added/removed + type: str + required: true + direction: + description: Direction to be applied for the ACL + type: str + required: true + choices: + - in + - out + port: + description: Use ACL as port policy. + type: bool + state: + description: The state the configuration should be left in + type: str + choices: + - deleted + - gathered + - merged + - overridden + - rendered + - replaced + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------ +# + +- name: Merge ACL interfaces configuration + cisco.nxos.nxos_acl_interfaces: + config: + - name: Ethernet1/2 + access_groups: + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + + - name: Eth1/5 + access_groups: + - afi: ipv4 + acls: + - name: PortACL + direction: in + port: true + + - name: ACL1v4 + direction: out + + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + state: merged + +# After state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +# Using replaced + +# Before state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +- name: Replace interface configuration with given configuration + cisco.nxos.nxos_acl_interfaces: + config: + - name: Eth1/5 + access_groups: + - afi: ipv4 + acls: + - name: NewACLv4 + direction: out + + - name: Ethernet1/3 + access_groups: + - afi: ipv6 + acls: + - name: NewACLv6 + direction: in + port: true + state: replaced + +# After state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/3 +# ipv6 port traffic-filter NewACLv6 in +# interface Ethernet1/5 +# ip access-group NewACLv4 out + +# Using overridden + +# Before state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +- name: Override interface configuration with given configuration + cisco.nxos.nxos_acl_interfaces: + config: + - name: Ethernet1/3 + access_groups: + - afi: ipv4 + acls: + - name: ACL1v4 + direction: out + + - name: PortACL + port: true + direction: in + - afi: ipv6 + acls: + - name: NewACLv6 + direction: in + port: true + state: overridden + +# After state: +# ------------ +# interface Ethernet1/3 +# ip access-group ACL1v4 out +# ip port access-group PortACL in +# ipv6 port traffic-filter NewACLv6 in + +# Using deleted to remove ACL config from specified interfaces + +# Before state: +# ------------- +# interface Ethernet1/1 +# ip access-group ACL2v4 in +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +- name: Delete ACL configuration on interfaces + cisco.nxos.nxos_acl_interfaces: + config: + - name: Ethernet1/5 + - name: Ethernet1/2 + state: deleted + +# After state: +# ------------- +# interface Ethernet1/1 +# ip access-group ACL2v4 in +# interface Ethernet1/2 +# interface Ethernet1/5 + +# Using deleted to remove ACL config from all interfaces + +# Before state: +# ------------- +# interface Ethernet1/1 +# ip access-group ACL2v4 in +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +- name: Delete ACL configuration from all interfaces + cisco.nxos.nxos_acl_interfaces: + state: deleted + +# After state: +# ------------- +# interface Ethernet1/1 +# interface Ethernet1/2 +# interface Ethernet1/5 + +# Using parsed + +- name: Parse given configuration into structured format + cisco.nxos.nxos_acl_interfaces: + running_config: | + interface Ethernet1/2 + ipv6 traffic-filter ACL1v6 in + interface Ethernet1/5 + ipv6 traffic-filter ACL1v6 in + ip access-group ACL1v4 out + ip port access-group PortACL in + state: parsed + +# returns +# parsed: +# - name: Ethernet1/2 +# access_groups: +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# direction: in +# - name: Ethernet1/5 +# access_groups: +# - afi: ipv4 +# acls: +# - name: PortACL +# direction: in +# port: True +# - name: ACL1v4 +# direction: out +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# direction: in + + +# Using gathered: + +# Before state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ipv6 traffic-filter ACL1v6 in +# ip access-group ACL1v4 out +# ip port access-group PortACL in + +- name: Gather existing configuration from device + cisco.nxos.nxos_acl_interfaces: + config: + state: gathered + +# returns +# gathered: +# - name: Ethernet1/2 +# access_groups: +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# direction: in +# - name: Ethernet1/5 +# access_groups: +# - afi: ipv4 +# acls: +# - name: PortACL +# direction: in +# port: True +# - name: ACL1v4 +# direction: out +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# direction: in + + +# Using rendered + +- name: Render required configuration to be pushed to the device + cisco.nxos.nxos_acl_interfaces: + config: + - name: Ethernet1/2 + access_groups: + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + + - name: Ethernet1/5 + access_groups: + - afi: ipv4 + acls: + - name: PortACL + direction: in + port: true + - name: ACL1v4 + direction: out + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + state: rendered + +# returns +# rendered: +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ipv6 traffic-filter ACL1v6 in +# ip access-group ACL1v4 out +# ip port access-group PortACL in + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['interface Ethernet1/2', 'ipv6 traffic-filter ACL1v6 out', 'ip port access-group PortACL in'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acl_interfaces.acl_interfaces import ( + Acl_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.acl_interfaces.acl_interfaces import ( + Acl_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Acl_interfacesArgs.argument_spec, + supports_check_mode=True, + ) + + result = Acl_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py new file mode 100644 index 00000000..51a66504 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py @@ -0,0 +1,915 @@ +#!/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 nxos_acls +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_acls +short_description: ACLs resource module +description: Manage named IP ACLs on the Cisco NX-OS platform +version_added: 1.0.0 +author: Adharsh Srivats Rangarajan (@adharshsrivatsr) +notes: +- Tested against NX-OS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- As NX-OS allows configuring a rule again with different sequence numbers, the user + is expected to provide sequence numbers for the access control entries to preserve + idempotency. If no sequence number is given, the rule will be added as a new rule + by the device. +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section 'ip(v6)* access-list). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dictionary of ACL options. + type: list + elements: dict + suboptions: + afi: + description: The Address Family Indicator (AFI) for the ACL. + type: str + required: true + choices: + - ipv4 + - ipv6 + acls: + description: A list of the ACLs. + type: list + elements: dict + suboptions: + name: + description: Name of the ACL. + type: str + required: true + aces: + description: The entries within the ACL. + type: list + elements: dict + suboptions: + grant: + description: Action to be applied on the rule. + type: str + choices: + - permit + - deny + destination: + description: Specify the packet destination. + type: dict + suboptions: + address: + description: Destination network address. + type: str + any: + description: Any destination address. + type: bool + host: + description: Host IP address. + type: str + port_protocol: + description: Specify the destination port or protocol (only for + TCP and UDP). + type: dict + suboptions: + eq: + description: Match only packets on a given port number. + type: str + gt: + description: Match only packets with a greater port number. + type: str + lt: + description: Match only packets with a lower port number. + type: str + neq: + description: Match only packets not on a given port number. + type: str + range: + description: Match only packets in the range of port numbers. + type: dict + suboptions: + start: + description: Specify the start of the port range. + type: str + end: + description: Specify the end of the port range. + type: str + prefix: + description: Destination network prefix. Only for prefixes of + value less than 31 for ipv4 and 127 for ipv6. Prefixes of 32 + (ipv4) and 128 (ipv6) should be given in the 'host' key. + type: str + wildcard_bits: + description: Destination wildcard bits. + type: str + dscp: + description: Match packets with given DSCP value. + type: str + fragments: + description: Check non-initial fragments. + type: bool + remark: + description: Access list entry comment. + type: str + sequence: + description: Sequence number. + type: int + source: + description: Specify the packet source. + type: dict + suboptions: + address: + description: Source network address. + type: str + any: + description: Any source address. + type: bool + host: + description: Host IP address. + type: str + port_protocol: + description: Specify the destination port or protocol (only for + TCP and UDP). + type: dict + suboptions: + eq: + description: Match only packets on a given port number. + type: str + gt: + description: Match only packets with a greater port number. + type: str + lt: + description: Match only packets with a lower port number. + type: str + neq: + description: Match only packets not on a given port number. + type: str + range: + description: Match only packets in the range of port numbers. + type: dict + suboptions: + start: + description: Specify the start of the port range. + type: str + end: + description: Specify the end of the port range. + type: str + prefix: + description: Source network prefix. Only for prefixes of mask + value less than 31 for ipv4 and 127 for ipv6. Prefixes of mask + 32 (ipv4) and 128 (ipv6) should be given in the 'host' key. + type: str + wildcard_bits: + description: Source wildcard bits. + type: str + log: + description: Log matches against this entry. + type: bool + precedence: + description: Match packets with given precedence value. + type: str + protocol: + description: Specify the protocol. + type: str + protocol_options: + description: All possible suboptions for the protocol chosen. + type: dict + suboptions: + icmp: + description: ICMP protocol options. + type: dict + suboptions: + administratively_prohibited: + description: Administratively prohibited + type: bool + alternate_address: + description: Alternate address + type: bool + conversion_error: + description: Datagram conversion + type: bool + dod_host_prohibited: + description: Host prohibited + type: bool + dod_net_prohibited: + description: Net prohibited + type: bool + echo: + description: Echo (ping) + type: bool + echo_reply: + description: Echo reply + type: bool + echo_request: + description: Echo request (ping) + type: bool + general_parameter_problem: + description: Parameter problem + type: bool + host_isolated: + description: Host isolated + type: bool + host_precedence_unreachable: + description: Host unreachable for precedence + type: bool + host_redirect: + description: Host redirect + type: bool + host_tos_redirect: + description: Host redirect for TOS + type: bool + host_tos_unreachable: + description: Host unreachable for TOS + type: bool + host_unknown: + description: Host unknown + type: bool + host_unreachable: + description: Host unreachable + type: bool + information_reply: + description: Information replies + type: bool + information_request: + description: Information requests + type: bool + mask_reply: + description: Mask replies + type: bool + mask_request: + description: Mask requests + type: bool + message_code: + description: ICMP message code + type: int + message_type: + description: ICMP message type + type: int + mobile_redirect: + description: Mobile host redirect + type: bool + net_redirect: + description: Network redirect + type: bool + net_tos_redirect: + description: Net redirect for TOS + type: bool + net_tos_unreachable: + description: Network unreachable for TOS + type: bool + net_unreachable: + description: Net unreachable + type: bool + network_unknown: + description: Network unknown + type: bool + no_room_for_option: + description: Parameter required but no room + type: bool + option_missing: + description: Parameter required but not present + type: bool + packet_too_big: + description: Fragmentation needed and DF set + type: bool + parameter_problem: + description: All parameter problems + type: bool + port_unreachable: + description: Port unreachable + type: bool + precedence_unreachable: + description: Precedence cutoff + type: bool + protocol_unreachable: + description: Protocol unreachable + type: bool + reassembly_timeout: + description: Reassembly timeout + type: bool + redirect: + description: All redirects + type: bool + router_advertisement: + description: Router discovery advertisements + type: bool + router_solicitation: + description: Router discovery solicitations + type: bool + source_quench: + description: Source quenches + type: bool + source_route_failed: + description: Source route failed + type: bool + time_exceeded: + description: All time exceeded. + type: bool + timestamp_reply: + description: Timestamp replies + type: bool + timestamp_request: + description: Timestamp requests + type: bool + traceroute: + description: Traceroute + type: bool + ttl_exceeded: + description: TTL exceeded + type: bool + unreachable: + description: All unreachables + type: bool + icmpv6: + description: ICMPv6 protocol options. + type: dict + suboptions: + beyond_scope: + description: Destination beyond scope. + type: bool + destination_unreachable: + description: Destination address is unreachable. + type: bool + echo_reply: + description: Echo reply. + type: bool + echo_request: + description: Echo request (ping). + type: bool + fragments: + description: Check non-initial fragments. + type: bool + header: + description: Parameter header problem. + type: bool + hop_limit: + description: Hop limit exceeded in transit. + type: bool + mld_query: + description: Multicast Listener Discovery Query. + type: bool + mld_reduction: + description: Multicast Listener Discovery Reduction. + type: bool + mld_report: + description: Multicast Listener Discovery Report. + type: bool + mldv2: + description: Multicast Listener Discovery Protocol. + type: bool + nd_na: + description: Neighbor discovery neighbor advertisements. + type: bool + nd_ns: + description: Neighbor discovery neighbor solicitations. + type: bool + next_header: + description: Parameter next header problems. + type: bool + no_admin: + description: Administration prohibited destination. + type: bool + no_route: + description: No route to destination. + type: bool + packet_too_big: + description: Packet too big. + type: bool + parameter_option: + description: Parameter option problems. + type: bool + parameter_problem: + description: All parameter problems. + type: bool + port_unreachable: + description: Port unreachable. + type: bool + reassembly_timeout: + description: Reassembly timeout. + type: bool + renum_command: + description: Router renumbering command. + type: bool + renum_result: + description: Router renumbering result. + type: bool + renum_seq_number: + description: Router renumbering sequence number reset. + type: bool + router_advertisement: + description: Neighbor discovery router advertisements. + type: bool + router_renumbering: + description: All router renumbering. + type: bool + router_solicitation: + description: Neighbor discovery router solicitations. + type: bool + time_exceeded: + description: All time exceeded. + type: bool + unreachable: + description: All unreachable. + type: bool + telemetry_path: + description: IPT enabled. + type: bool + telemetry_queue: + description: Flow of interest for BDC/HDC. + type: bool + tcp: + description: TCP flags. + type: dict + suboptions: + ack: + description: Match on the ACK bit + type: bool + established: + description: Match established connections + type: bool + fin: + description: Match on the FIN bit + type: bool + psh: + description: Match on the PSH bit + type: bool + rst: + description: Match on the RST bit + type: bool + syn: + description: Match on the SYN bit + type: bool + urg: + description: Match on the URG bit + type: bool + igmp: + description: IGMP protocol options. + type: dict + suboptions: + dvmrp: + description: Distance Vector Multicast Routing Protocol + type: bool + host_query: + description: Host Query + type: bool + host_report: + description: Host Report + type: bool + state: + description: + - The state the configuration should be left in + type: str + choices: + - deleted + - gathered + - merged + - overridden + - rendered + - replaced + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# + +- name: Merge new ACLs configuration + cisco.nxos.nxos_acls: + config: + - afi: ipv4 + acls: + - name: ACL1v4 + aces: + - grant: deny + destination: + address: 192.0.2.64 + wildcard_bits: 0.0.0.255 + source: + any: true + port_protocol: + lt: 55 + protocol: tcp + protocol_options: + tcp: + ack: true + fin: true + sequence: 50 + + - afi: ipv6 + acls: + - name: ACL1v6 + aces: + - grant: permit + sequence: 10 + source: + any: true + destination: + prefix: 2001:db8:12::/32 + protocol: sctp + state: merged + +# After state: +# ------------ +# +# ip access-list ACL1v4 +# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin +# ipv6 access-list ACL1v6 +# 10 permit sctp any any + +# Using replaced + +# Before state: +# ---------------- +# +# ip access-list ACL1v4 +# 10 permit ip any any +# 20 deny udp any any +# ip access-list ACL2v4 +# 10 permit ahp 192.0.2.0 0.0.0.255 any +# ip access-list ACL1v6 +# 10 permit sctp any any +# 20 remark IPv6 ACL +# ip access-list ACL2v6 +# 10 deny ipv6 any 2001:db8:3000::/36 +# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128 + +- name: Replace existing ACL configuration with provided configuration + cisco.nxos.nxos_acls: + config: + - afi: ipv4 + - afi: ipv6 + acls: + - name: ACL1v6 + aces: + - sequence: 20 + grant: permit + source: + any: true + destination: + any: true + protocol: pip + + - remark: Replaced ACE + + - name: ACL2v6 + state: replaced + +# After state: +# --------------- +# +# ipv6 access-list ACL1v6 +# 20 permit pip any any +# 30 remark Replaced ACE +# ipv6 access-list ACL2v6 + +# Using overridden + +# Before state: +# ---------------- +# +# ip access-list ACL1v4 +# 10 permit ip any any +# 20 deny udp any any +# ip access-list ACL2v4 +# 10 permit ahp 192.0.2.0 0.0.0.255 any +# ip access-list ACL1v6 +# 10 permit sctp any any +# 20 remark IPv6 ACL +# ip access-list ACL2v6 +# 10 deny ipv6 any 2001:db8:3000::/36 +# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128 + +- name: Override existing configuration with provided configuration + cisco.nxos.nxos_acls: + config: + - afi: ipv4 + acls: + - name: NewACL + aces: + - grant: deny + source: + address: 192.0.2.0 + wildcard_bits: 0.0.255.255 + destination: + any: true + protocol: eigrp + - remark: Example for overridden state + state: overridden + +# After state: +# ------------ +# +# ip access-list NewACL +# 10 deny eigrp 192.0.2.0 0.0.255.255 any +# 20 remark Example for overridden state + +# Using deleted: +# +# Before state: +# ------------- +# +# ip access-list ACL1v4 +# 10 permit ip any any +# 20 deny udp any any +# ip access-list ACL2v4 +# 10 permit ahp 192.0.2.0 0.0.0.255 any +# ip access-list ACL1v6 +# 10 permit sctp any any +# 20 remark IPv6 ACL +# ip access-list ACL2v6 +# 10 deny ipv6 any 2001:db8:3000::/36 +# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128 + +- name: Delete all ACLs + cisco.nxos.nxos_acls: + config: + state: deleted + +# After state: +# ----------- +# + + +# Before state: +# ------------- +# +# ip access-list ACL1v4 +# 10 permit ip any any +# 20 deny udp any any +# ip access-list ACL2v4 +# 10 permit ahp 192.0.2.0 0.0.0.255 any +# ip access-list ACL1v6 +# 10 permit sctp any any +# 20 remark IPv6 ACL +# ip access-list ACL2v6 +# 10 deny ipv6 any 2001:db8:3000::/36 +# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128 + +- name: Delete all ACLs in given AFI + cisco.nxos.nxos_acls: + config: + - afi: ipv4 + state: deleted + +# After state: +# ------------ +# +# ip access-list ACL1v6 +# 10 permit sctp any any +# 20 remark IPv6 ACL +# ip access-list ACL2v6 +# 10 deny ipv6 any 2001:db8:3000::/36 +# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128 + + + +# Before state: +# ------------- +# +# ip access-list ACL1v4 +# 10 permit ip any any +# 20 deny udp any any +# ip access-list ACL2v4 +# 10 permit ahp 192.0.2.0 0.0.0.255 any +# ipv6 access-list ACL1v6 +# 10 permit sctp any any +# 20 remark IPv6 ACL +# ipv6 access-list ACL2v6 +# 10 deny ipv6 any 2001:db8:3000::/36 +# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128 + +- name: Delete specific ACLs + cisco.nxos.nxos_acls: + config: + - afi: ipv4 + acls: + - name: ACL1v4 + - name: ACL2v4 + - afi: ipv6 + acls: + - name: ACL1v6 + state: deleted + +# After state: +# ------------ +# ipv6 access-list ACL2v6 +# 10 deny ipv6 any 2001:db8:3000::/36 +# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128 + +# Using parsed + +- name: Parse given config to structured data + cisco.nxos.nxos_acls: + running_config: | + ip access-list ACL1v4 + 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin + ipv6 access-list ACL1v6 + 10 permit sctp any any + state: parsed + +# returns: +# parsed: +# - afi: ipv4 +# acls: +# - name: ACL1v4 +# aces: +# - grant: deny +# destination: +# address: 192.0.2.64 +# wildcard_bits: 0.0.0.255 +# source: +# any: true +# port_protocol: +# lt: 55 +# protocol: tcp +# protocol_options: +# tcp: +# ack: true +# fin: true +# sequence: 50 +# +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# aces: +# - grant: permit +# sequence: 10 +# source: +# any: true +# destination: +# prefix: 2001:db8:12::/32 +# protocol: sctp + + +# Using gathered: + +# Before state: +# ------------ +# +# ip access-list ACL1v4 +# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin +# ipv6 access-list ACL1v6 +# 10 permit sctp any any + +- name: Gather existing configuration + cisco.nxos.nxos_acls: + state: gathered + +# returns: +# gathered: +# - afi: ipv4 +# acls: +# - name: ACL1v4 +# aces: +# - grant: deny +# destination: +# address: 192.0.2.64 +# wildcard_bits: 0.0.0.255 +# source: +# any: true +# port_protocol: +# lt: 55 +# protocol: tcp +# protocol_options: +# tcp: +# ack: true +# fin: true +# sequence: 50 + +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# aces: +# - grant: permit +# sequence: 10 +# source: +# any: true +# destination: +# prefix: 2001:db8:12::/32 +# protocol: sctp + + +# Using rendered + +- name: Render required configuration to be pushed to the device + cisco.nxos.nxos_acls: + config: + - afi: ipv4 + acls: + - name: ACL1v4 + aces: + - grant: deny + destination: + address: 192.0.2.64 + wildcard_bits: 0.0.0.255 + source: + any: true + port_protocol: + lt: 55 + protocol: tcp + protocol_options: + tcp: + ack: true + fin: true + sequence: 50 + + - afi: ipv6 + acls: + - name: ACL1v6 + aces: + - grant: permit + sequence: 10 + source: + any: true + destination: + prefix: 2001:db8:12::/32 + protocol: sctp + state: rendered + +# returns: +# rendered: +# ip access-list ACL1v4 +# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin +# ipv6 access-list ACL1v6 +# 10 permit sctp any any +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['ip access-list ACL1v4', '10 permit ip any any precedence critical log', '20 deny tcp any lt smtp host 192.0.2.64 ack fin'] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acls.acls import ( + AclsArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.acls.acls import Acls + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=AclsArgs.argument_spec, supports_check_mode=True) + + result = Acls(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py new file mode 100644 index 00000000..f946dc0a --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +# (c) 2017, Ansible by Red Hat, inc +# +# This file is part of Ansible by Red Hat +# +# 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/>. +# + + +DOCUMENTATION = """ +module: nxos_banner +author: Trishna Guha (@trishnaguha) +short_description: Manage multiline banners on Cisco NXOS devices +description: +- This will configure both exec and motd banners on remote devices running Cisco NXOS. + It allows playbooks to add or remove banner text from the active running configuration. +notes: +- Since responses from the device are always read with surrounding whitespaces stripped, + tasks that configure banners with preceeding or trailing whitespaces will not be idempotent. +- Limited Support for Cisco MDS +version_added: 1.0.0 +options: + banner: + description: + - Specifies which banner that should be configured on the remote device. + required: true + choices: + - exec + - motd + type: str + text: + description: + - The banner text that should be present in the remote device running configuration. + This argument accepts a multiline string, with no empty lines. Requires I(state=present). + type: str + state: + description: + - Specifies whether or not the configuration is present in the current devices + active running configuration. + default: present + choices: + - present + - absent + type: str +extends_documentation_fragment: +- cisco.nxos.nxos +""" + +EXAMPLES = """ +- name: configure the exec banner + cisco.nxos.nxos_banner: + banner: exec + text: | + this is my exec banner + that contains a multiline + string + state: present +- name: remove the motd banner + cisco.nxos.nxos_banner: + banner: motd + state: absent +- name: Configure banner from file + cisco.nxos.nxos_banner: + banner: motd + text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}" + state: present +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - banner exec + - this is my exec banner + - that contains a multiline + - string +""" + +import re + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(module, command): + format = "text" + cmds = [{"command": command, "output": format}] + output = run_commands(module, cmds) + return output + + +def map_obj_to_commands(want, have, module): + commands = list() + state = module.params["state"] + platform_regex = "Nexus.*Switch" + + if state == "absent": + if have.get("text") and not ( + (have.get("text") == "User Access Verification") + or re.match(platform_regex, have.get("text")) + ): + commands.append("no banner %s" % module.params["banner"]) + + elif state == "present" and want.get("text") != have.get("text"): + banner_cmd = "banner %s @\n%s\n@" % ( + module.params["banner"], + want["text"], + ) + commands.append(banner_cmd) + + return commands + + +def map_config_to_obj(module): + command = "show banner %s" % module.params["banner"] + output = execute_show_command(module, command)[0] + + if "Invalid command" in output: + module.fail_json( + msg="banner: %s may not be supported on this platform. Possible values are : exec | motd" + % module.params["banner"], + ) + + if isinstance(output, dict): + output = list(output.values()) + if output != []: + output = output[0] + else: + output = "" + if isinstance(output, dict): + output = list(output.values()) + if output != []: + output = output[0] + else: + output = "" + else: + output = output.rstrip() + + obj = {"banner": module.params["banner"], "state": "absent"} + if output: + obj["text"] = output + obj["state"] = "present" + return obj + + +def map_params_to_obj(module): + text = module.params["text"] + return { + "banner": module.params["banner"], + "text": to_text(text) if text else None, + "state": module.params["state"], + } + + +def main(): + """main entry point for module execution""" + argument_spec = dict( + banner=dict(required=True, choices=["exec", "motd"]), + text=dict(), + state=dict(default="present", choices=["present", "absent"]), + ) + + required_if = [("state", "present", ("text",))] + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True, + ) + + warnings = list() + + result = {"changed": False} + if warnings: + result["warnings"] = warnings + want = map_params_to_obj(module) + have = map_config_to_obj(module) + commands = map_obj_to_commands(want, have, module) + result["commands"] = commands + + if commands: + if not module.check_mode: + msgs = load_config(module, commands, True) + if msgs: + for item in msgs: + if item: + if isinstance(item, dict): + err_str = item["clierror"] + else: + err_str = item + if "more than 40 lines" in err_str or "buffer overflowed" in err_str: + load_config(module, commands) + + result["changed"] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py new file mode 100644 index 00000000..6ae1a88d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py @@ -0,0 +1,333 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_bfd_global +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Bidirectional Forwarding Detection (BFD) global-level configuration +description: +- Manages Bidirectional Forwarding Detection (BFD) global-level configuration. +version_added: 1.0.0 +author: +- Chris Van Heuveln (@chrisvanheuveln) +notes: +- Tested against NXOSv 9.2(2) +- Unsupported for Cisco MDS +- BFD global will automatically enable 'feature bfd' if it is disabled. +- BFD global does not have a 'state' parameter. All of the BFD commands are unique + and are defined if 'feature bfd' is enabled. +options: + echo_interface: + description: + - Loopback interface used for echo frames. + - Valid values are loopback interface name or 'deleted'. + - Not supported on N5K/N6K + required: false + type: str + echo_rx_interval: + description: + - BFD Echo receive interval in milliseconds. + required: false + type: int + interval: + description: + - BFD interval timer values. + - Value must be a dict defining values for keys (tx, min_rx, and multiplier) + required: false + type: dict + slow_timer: + description: + - BFD slow rate timer in milliseconds. + required: false + type: int + startup_timer: + description: + - BFD delayed startup timer in seconds. + - Not supported on N5K/N6K/N7K + required: false + type: int + ipv4_echo_rx_interval: + description: + - BFD IPv4 session echo receive interval in milliseconds. + required: false + type: int + ipv4_interval: + description: + - BFD IPv4 interval timer values. + - Value must be a dict defining values for keys (tx, min_rx, and multiplier). + required: false + type: dict + ipv4_slow_timer: + description: + - BFD IPv4 slow rate timer in milliseconds. + required: false + type: int + ipv6_echo_rx_interval: + description: + - BFD IPv6 session echo receive interval in milliseconds. + required: false + type: int + ipv6_interval: + description: + - BFD IPv6 interval timer values. + - Value must be a dict defining values for keys (tx, min_rx, and multiplier). + required: false + type: dict + ipv6_slow_timer: + description: + - BFD IPv6 slow rate timer in milliseconds. + required: false + type: int + fabricpath_interval: + description: + - BFD fabricpath interval timer values. + - Value must be a dict defining values for keys (tx, min_rx, and multiplier). + required: false + type: dict + fabricpath_slow_timer: + description: + - BFD fabricpath slow rate timer in milliseconds. + required: false + type: int + fabricpath_vlan: + description: + - BFD fabricpath control vlan. + required: false + type: int +""" +EXAMPLES = """ +- cisco.nxos.nxos_bfd_global: + echo_interface: Ethernet1/2 + echo_rx_interval: 50 + interval: + tx: 50 + min_rx: 50 + multiplier: 4 +""" + +RETURN = """ +cmds: + description: commands sent to the device + returned: always + type: list + sample: ["bfd echo-interface loopback1", "bfd slow-timer 2000"] +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + NxosCmdRef, + load_config, +) + + +BFD_CMD_REF = """ +# The cmd_ref is a yaml formatted list of module commands. +# A leading underscore denotes a non-command variable; e.g. _template. +# BFD does not have convenient json data so this cmd_ref uses raw cli configs. +--- +_template: # _template holds common settings for all commands + # Enable feature bfd if disabled + feature: bfd + # Common get syntax for BFD commands + get_command: show run bfd all | incl '^(no )*bfd' + +echo_interface: + kind: str + getval: (no )*bfd echo-interface *(\\S+)*$ + setval: 'bfd echo-interface {0}' + default: ~ + +echo_rx_interval: + _exclude: ['N5K', 'N6K'] + kind: int + getval: bfd echo-rx-interval (\\d+)$ + setval: bfd echo-rx-interval {0} + default: 50 + N3K: + default: 250 + +interval: + kind: dict + getval: bfd interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+) + setval: bfd interval {tx} min_rx {min_rx} multiplier {multiplier} + default: &def_interval + tx: 50 + min_rx: 50 + multiplier: 3 + N3K: + default: &n3k_def_interval + tx: 250 + min_rx: 250 + multiplier: 3 + +slow_timer: + kind: int + getval: bfd slow-timer (\\d+)$ + setval: bfd slow-timer {0} + default: 2000 + +startup_timer: + _exclude: ['N5K', 'N6K', 'N7K'] + kind: int + getval: bfd startup-timer (\\d+)$ + setval: bfd startup-timer {0} + default: 5 + +# IPv4/IPv6 specific commands +ipv4_echo_rx_interval: + _exclude: ['N5K', 'N6K'] + kind: int + getval: bfd ipv4 echo-rx-interval (\\d+)$ + setval: bfd ipv4 echo-rx-interval {0} + default: 50 + N3K: + default: 250 + +ipv4_interval: + _exclude: ['N5K', 'N6K'] + kind: dict + getval: bfd ipv4 interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+) + setval: bfd ipv4 interval {tx} min_rx {min_rx} multiplier {multiplier} + default: *def_interval + N3K: + default: *n3k_def_interval + +ipv4_slow_timer: + _exclude: ['N5K', 'N6K'] + kind: int + getval: bfd ipv4 slow-timer (\\d+)$ + setval: bfd ipv4 slow-timer {0} + default: 2000 + +ipv6_echo_rx_interval: + _exclude: ['N35', 'N5K', 'N6K'] + kind: int + getval: bfd ipv6 echo-rx-interval (\\d+)$ + setval: bfd ipv6 echo-rx-interval {0} + default: 50 + N3K: + default: 250 + +ipv6_interval: + _exclude: ['N35', 'N5K', 'N6K'] + kind: dict + getval: bfd ipv6 interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+) + setval: bfd ipv6 interval {tx} min_rx {min_rx} multiplier {multiplier} + default: *def_interval + N3K: + default: *n3k_def_interval + +ipv6_slow_timer: + _exclude: ['N35', 'N5K', 'N6K'] + kind: int + getval: bfd ipv6 slow-timer (\\d+)$ + setval: bfd ipv6 slow-timer {0} + default: 2000 + +# Fabricpath Commands +fabricpath_interval: + _exclude: ['N35', 'N3K', 'N9K'] + kind: dict + getval: bfd fabricpath interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+) + setval: bfd fabricpath interval {tx} min_rx {min_rx} multiplier {multiplier} + default: *def_interval + +fabricpath_slow_timer: + _exclude: ['N35', 'N3K', 'N9K'] + kind: int + getval: bfd fabricpath slow-timer (\\d+)$ + setval: bfd fabricpath slow-timer {0} + default: 2000 + +fabricpath_vlan: + _exclude: ['N35', 'N3K', 'N9K'] + kind: int + getval: bfd fabricpath vlan (\\d+)$ + setval: bfd fabricpath vlan {0} + default: 1 +""" + + +def reorder_cmds(cmds): + """ + There is a bug in some image versions where bfd echo-interface and + bfd echo-rx-interval need to be applied last for them to nvgen properly. + """ + regex1 = re.compile(r"^bfd echo-interface") + regex2 = re.compile(r"^bfd echo-rx-interval") + filtered_cmds = [i for i in cmds if not regex1.match(i)] + filtered_cmds = [i for i in filtered_cmds if not regex2.match(i)] + echo_int_cmd = [i for i in cmds if regex1.match(i)] + echo_rx_cmd = [i for i in cmds if regex2.match(i)] + filtered_cmds.extend(echo_int_cmd) + filtered_cmds.extend(echo_rx_cmd) + + return filtered_cmds + + +def main(): + argument_spec = dict( + echo_interface=dict(required=False, type="str"), + echo_rx_interval=dict(required=False, type="int"), + interval=dict(required=False, type="dict"), + slow_timer=dict(required=False, type="int"), + startup_timer=dict(required=False, type="int"), + ipv4_echo_rx_interval=dict(required=False, type="int"), + ipv4_interval=dict(required=False, type="dict"), + ipv4_slow_timer=dict(required=False, type="int"), + ipv6_echo_rx_interval=dict(required=False, type="int"), + ipv6_interval=dict(required=False, type="dict"), + ipv6_slow_timer=dict(required=False, type="int"), + fabricpath_interval=dict(required=False, type="dict"), + fabricpath_slow_timer=dict(required=False, type="int"), + fabricpath_vlan=dict(required=False, type="int"), + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + warnings = list() + + cmd_ref = NxosCmdRef(module, BFD_CMD_REF) + cmd_ref.get_existing() + cmd_ref.get_playvals() + cmds = reorder_cmds(cmd_ref.get_proposed()) + + result = { + "changed": False, + "commands": cmds, + "warnings": warnings, + "check_mode": module.check_mode, + } + if cmds: + result["changed"] = True + if not module.check_mode: + load_config(module, cmds) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py new file mode 100644 index 00000000..1790f8e0 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py @@ -0,0 +1,302 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# 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 nxos_bfd_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_bfd_interfaces +short_description: BFD interfaces resource module +description: Manages attributes of Bidirectional Forwarding Detection (BFD) on the + interface. +version_added: 1.0.0 +author: Chris Van Heuveln (@chrisvanheuveln) +notes: +- Tested against NX-OS 7.0(3)I5(1). +- Unsupported for Cisco MDS +- Feature bfd should be enabled for this module. +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^interface|^feature + bfd'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: The provided configuration + type: list + elements: dict + suboptions: + name: + type: str + description: The name of the interface. + bfd: + type: str + description: + - Enable/Disable Bidirectional Forwarding Detection (BFD) on the interface. + choices: + - enable + - disable + echo: + type: str + description: + - Enable/Disable BFD Echo functionality on the interface. + choices: + - enable + - disable + state: + description: + - The state of the configuration after module completion + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using deleted + +- name: Configure interfaces + cisco.nxos.nxos_bfd_interfaces: + state: deleted + + +# Using merged + +- name: Configure interfaces + cisco.nxos.nxos_bfd_interfaces: + config: + - name: Ethernet1/1 + bfd: enable + echo: enable + - name: Ethernet1/2 + bfd: disable + echo: disable + state: merged + + +# Using overridden + +- name: Configure interfaces + cisco.nxos.nxos_bfd_interfaces: + config: + - name: Ethernet1/1 + bfd: enable + echo: enable + - name: Ethernet1/2 + bfd: disable + echo: disable + state: overridden + + +# Using replaced + +- name: Configure interfaces + cisco.nxos.nxos_bfd_interfaces: + config: + - name: Ethernet1/1 + bfd: enable + echo: enable + - name: Ethernet1/2 + bfd: disable + echo: disable + state: replaced + +# Using rendered + +- name: Use rendered state to convert task input to device specific commands + cisco.nxos.nxos_bfd_interfaces: + config: + - name: Ethernet1/800 + bfd: enable + echo: enable + - name: Ethernet1/801 + bfd: disable + echo: disable + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "interface Ethernet1/800" +# - "bfd" +# - "bfd echo" +# - "interface Ethernet1/801" +# - "no bfd" +# - "no bfd echo" + +# Using parsed + +# parsed.cfg +# ------------ + +# feature bfd +# interface Ethernet1/800 +# no switchport +# no bfd +# no bfd echo +# interface Ethernet1/801 +# no switchport +# no bfd +# interface Ethernet1/802 +# no switchport +# no bfd echo +# interface mgmt0 +# ip address dhcp +# vrf member management + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_bfd_interfaces: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- + +# parsed: +# - bfd: disable +# echo: disable +# name: Ethernet1/800 +# - bfd: disable +# echo: enable +# name: Ethernet1/801 +# - bfd: enable +# echo: disable +# name: Ethernet1/802 +# - bfd: enable +# echo: enable +# name: mgmt0 + +# Using gathered + +# Existing device config state +# ------------------------------- + +# feature bfd +# interface Ethernet1/1 +# no switchport +# no bfd +# interface Ethernet1/2 +# no switchport +# no bfd echo +# interface mgmt0 +# ip address dhcp +# vrf member management + +- name: Gather bfd_interfaces facts from the device using nxos_bfd_interfaces + cisco.nxos.nxos_bfd_interfaces: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# - name: Ethernet1/1 +# bfd: disable +# echo: enable +# - name: Ethernet1/3 +# echo: disable +# bfd: enable +# - name: mgmt0 +# bfd: enable +# echo: enable +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: ['interface Ethernet1/1', 'no bfd', 'no bfd echo'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bfd_interfaces.bfd_interfaces import ( + Bfd_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bfd_interfaces.bfd_interfaces import ( + Bfd_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=Bfd_interfacesArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = Bfd_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py new file mode 100644 index 00000000..2c56f412 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py @@ -0,0 +1,761 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_bgp +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2023-01-27) Manages BGP configuration. +description: +- Manages BGP configurations on NX-OS switches. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +deprecated: + alternative: nxos_bgp_global + why: Updated module released with more functionality. + removed_at_date: '2023-01-27' +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- C(state=absent) removes the whole BGP ASN configuration when C(vrf=default) or the + whole VRF instance within the BGP process when using a different VRF. +- Default when supported restores params default value. +- Configuring global params is only permitted if C(vrf=default). +options: + asn: + description: + - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or + ASDOT notation. + required: true + type: str + vrf: + description: + - Name of the VRF. The name 'default' is a valid VRF representing the global BGP. + default: 'default' + type: str + bestpath_always_compare_med: + description: + - Enable/Disable MED comparison on paths from different autonomous systems. + type: bool + bestpath_aspath_multipath_relax: + description: + - Enable/Disable load sharing across the providers with different (but equal-length) + AS paths. + type: bool + bestpath_compare_routerid: + description: + - Enable/Disable comparison of router IDs for identical eBGP paths. + type: bool + bestpath_compare_neighborid: + description: + - Enable/Disable neighborid. Use this when more paths available than max path + config. + type: bool + bestpath_cost_community_ignore: + description: + - Enable/Disable Ignores the cost community for BGP best-path calculations. + type: bool + bestpath_med_confed: + description: + - Enable/Disable enforcement of bestpath to do a MED comparison only between paths + originated within a confederation. + type: bool + bestpath_med_missing_as_worst: + description: + - Enable/Disable assigns the value of infinity to received routes that do not + carry the MED attribute, making these routes the least desirable. + type: bool + bestpath_med_non_deterministic: + description: + - Enable/Disable deterministic selection of the best MED pat from among the paths + from the same autonomous system. + type: bool + cluster_id: + description: + - Route Reflector Cluster-ID. + type: str + confederation_id: + description: + - Routing domain confederation AS. + type: str + confederation_peers: + description: + - AS confederation parameters. + type: list + elements: str + disable_policy_batching: + description: + - Enable/Disable the batching evaluation of prefix advertisement to all peers. + type: bool + disable_policy_batching_ipv4_prefix_list: + description: + - Enable/Disable the batching evaluation of prefix advertisements to all peers + with prefix list. + type: str + disable_policy_batching_ipv6_prefix_list: + description: + - Enable/Disable the batching evaluation of prefix advertisements to all peers + with prefix list. + type: str + enforce_first_as: + description: + - Enable/Disable enforces the neighbor autonomous system to be the first AS number + listed in the AS path attribute for eBGP. On NX-OS, this property is only supported + in the global BGP context. + type: bool + event_history_cli: + description: + - Enable/Disable cli event history buffer. + choices: + - size_small + - size_medium + - size_large + - size_disable + - default + - 'true' + - 'false' + type: str + event_history_detail: + description: + - Enable/Disable detail event history buffer. + choices: + - size_small + - size_medium + - size_large + - size_disable + - default + - 'true' + - 'false' + type: str + event_history_events: + description: + - Enable/Disable event history buffer. + choices: + - size_small + - size_medium + - size_large + - size_disable + - default + - 'true' + - 'false' + type: str + event_history_periodic: + description: + - Enable/Disable periodic event history buffer. + choices: + - size_small + - size_medium + - size_large + - size_disable + - default + - 'true' + - 'false' + type: str + fast_external_fallover: + description: + - Enable/Disable immediately reset the session if the link to a directly connected + BGP peer goes down. Only supported in the global BGP context. + type: bool + flush_routes: + description: + - Enable/Disable flush routes in RIB upon controlled restart. On NX-OS, this property + is only supported in the global BGP context. + type: bool + graceful_restart: + description: + - Enable/Disable graceful restart. + type: bool + graceful_restart_helper: + description: + - Enable/Disable graceful restart helper mode. + type: bool + graceful_restart_timers_restart: + description: + - Set maximum time for a restart sent to the BGP peer. + type: str + graceful_restart_timers_stalepath_time: + description: + - Set maximum time that BGP keeps the stale routes from the restarting BGP peer. + type: str + isolate: + description: + - Enable/Disable isolate this router from BGP perspective. + type: bool + local_as: + description: + - Local AS number to be used within a VRF instance. + type: str + log_neighbor_changes: + description: + - Enable/Disable message logging for neighbor up/down event. + type: bool + maxas_limit: + description: + - Specify Maximum number of AS numbers allowed in the AS-path attribute. Valid + values are between 1 and 512. + type: str + neighbor_down_fib_accelerate: + description: + - Enable/Disable handle BGP neighbor down event, due to various reasons. + type: bool + reconnect_interval: + description: + - The BGP reconnection interval for dropped sessions. Valid values are between + 1 and 60. + type: str + router_id: + description: + - Router Identifier (ID) of the BGP router VRF instance. + type: str + shutdown: + description: + - Administratively shutdown the BGP protocol. + type: bool + suppress_fib_pending: + description: + - Enable/Disable advertise only routes programmed in hardware to peers. + type: bool + timer_bestpath_limit: + description: + - Specify timeout for the first best path after a restart, in seconds. + type: str + timer_bgp_hold: + description: + - Set BGP hold timer. + type: str + timer_bgp_keepalive: + description: + - Set BGP keepalive timer. + type: str + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: + - present + - absent + type: str +""" + + +EXAMPLES = """ +- name: Configure a simple ASN + cisco.nxos.nxos_bgp: + asn: 65535 + vrf: test + router_id: 192.0.2.1 + state: present +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["router bgp 65535", "vrf test", "router-id 192.0.2.1"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +BOOL_PARAMS = [ + "bestpath_always_compare_med", + "bestpath_aspath_multipath_relax", + "bestpath_compare_neighborid", + "bestpath_compare_routerid", + "bestpath_cost_community_ignore", + "bestpath_med_confed", + "bestpath_med_missing_as_worst", + "bestpath_med_non_deterministic", + "disable_policy_batching", + "enforce_first_as", + "fast_external_fallover", + "flush_routes", + "graceful_restart", + "graceful_restart_helper", + "isolate", + "log_neighbor_changes", + "neighbor_down_fib_accelerate", + "shutdown", + "suppress_fib_pending", +] +GLOBAL_PARAMS = [ + "disable_policy_batching", + "disable_policy_batching_ipv4_prefix_list", + "disable_policy_batching_ipv6_prefix_list", + "enforce_first_as", + "event_history_cli", + "event_history_detail", + "event_history_events", + "event_history_periodic", + "fast_external_fallover", + "flush_routes", + "isolate", + "suppress_fib_pending", + "shutdown", +] +PARAM_TO_DEFAULT_KEYMAP = { + "timer_bgp_keepalive": "60", + "timer_bgp_hold": "180", + "timer_bestpath_limit": "300", + "graceful_restart": True, + "graceful_restart_timers_restart": "120", + "graceful_restart_timers_stalepath_time": "300", + "reconnect_interval": "60", + "suppress_fib_pending": True, + "fast_external_fallover": True, + "enforce_first_as": True, + "event_history_cli": True, + "event_history_detail": False, + "event_history_events": True, + "event_history_periodic": True, + "maxas_limit": "", + "router_id": "", + "cluster_id": "", + "disable_policy_batching_ipv4_prefix_list": "", + "disable_policy_batching_ipv6_prefix_list": "", + "local_as": "", + "confederation_id": "", +} +PARAM_TO_COMMAND_KEYMAP = { + "asn": "router bgp", + "bestpath_always_compare_med": "bestpath always-compare-med", + "bestpath_aspath_multipath_relax": "bestpath as-path multipath-relax", + "bestpath_compare_neighborid": "bestpath compare-neighborid", + "bestpath_compare_routerid": "bestpath compare-routerid", + "bestpath_cost_community_ignore": "bestpath cost-community ignore", + "bestpath_med_confed": "bestpath med confed", + "bestpath_med_missing_as_worst": "bestpath med missing-as-worst", + "bestpath_med_non_deterministic": "bestpath med non-deterministic", + "cluster_id": "cluster-id", + "confederation_id": "confederation identifier", + "confederation_peers": "confederation peers", + "disable_policy_batching": "disable-policy-batching", + "disable_policy_batching_ipv4_prefix_list": "disable-policy-batching ipv4 prefix-list", + "disable_policy_batching_ipv6_prefix_list": "disable-policy-batching ipv6 prefix-list", + "enforce_first_as": "enforce-first-as", + "event_history_cli": "event-history cli", + "event_history_detail": "event-history detail", + "event_history_events": "event-history events", + "event_history_periodic": "event-history periodic", + "fast_external_fallover": "fast-external-fallover", + "flush_routes": "flush-routes", + "graceful_restart": "graceful-restart", + "graceful_restart_helper": "graceful-restart-helper", + "graceful_restart_timers_restart": "graceful-restart restart-time", + "graceful_restart_timers_stalepath_time": "graceful-restart stalepath-time", + "isolate": "isolate", + "local_as": "local-as", + "log_neighbor_changes": "log-neighbor-changes", + "maxas_limit": "maxas-limit", + "neighbor_down_fib_accelerate": "neighbor-down fib-accelerate", + "reconnect_interval": "reconnect-interval", + "router_id": "router-id", + "shutdown": "shutdown", + "suppress_fib_pending": "suppress-fib-pending", + "timer_bestpath_limit": "timers bestpath-limit", + "timer_bgp_hold": "timers bgp", + "timer_bgp_keepalive": "timers bgp", + "vrf": "vrf", +} + + +def get_value(arg, config): + command = PARAM_TO_COMMAND_KEYMAP.get(arg) + + if command.split()[0] == "event-history": + has_size = re.search(r"^\s+{0} size\s(?P<value>.*)$".format(command), config, re.M) + + if command == "event-history detail": + value = False + else: + value = "size_small" + + if has_size: + value = "size_%s" % has_size.group("value") + + elif arg in ["enforce_first_as", "fast_external_fallover"]: + no_command_re = re.compile(r"no\s+{0}\s*".format(command), re.M) + value = True + + if no_command_re.search(config): + value = False + + elif arg in BOOL_PARAMS: + has_command = re.search(r"^\s+{0}\s*$".format(command), config, re.M) + value = False + + if has_command: + value = True + else: + command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)".format(command), re.M) + value = "" + + has_command = command_val_re.search(config) + if has_command: + found_value = has_command.group("value") + + if arg == "confederation_peers": + value = found_value.split() + elif arg == "timer_bgp_keepalive": + value = found_value.split()[0] + elif arg == "timer_bgp_hold": + split_values = found_value.split() + if len(split_values) == 2: + value = split_values[1] + elif found_value: + value = found_value + + return value + + +def get_existing(module, args, warnings): + existing = {} + netcfg = CustomNetworkConfig(indent=2, contents=get_config(module, flags=["bgp all"])) + + asn_re = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S) + asn_match = asn_re.match(str(netcfg)) + + if asn_match: + existing_asn = asn_match.group("existing_asn") + bgp_parent = "router bgp {0}".format(existing_asn) + + if module.params["vrf"] != "default": + parents = [bgp_parent, "vrf {0}".format(module.params["vrf"])] + else: + parents = [bgp_parent] + + config = netcfg.get_section(parents) + if config: + for arg in args: + if arg != "asn" and (module.params["vrf"] == "default" or arg not in GLOBAL_PARAMS): + existing[arg] = get_value(arg, config) + + existing["asn"] = existing_asn + if module.params["vrf"] == "default": + existing["vrf"] = "default" + + if not existing and module.params["vrf"] != "default" and module.params["state"] == "present": + msg = "VRF {0} doesn't exist.".format(module.params["vrf"]) + warnings.append(msg) + + return existing + + +def apply_key_map(key_map, table): + new_dict = {} + for key in table: + new_key = key_map.get(key) + if new_key: + new_dict[new_key] = table.get(key) + + return new_dict + + +def state_present(module, existing, proposed, candidate): + commands = list() + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) + existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) + + for key, value in proposed_commands.items(): + if value is True: + commands.append(key) + elif value is False: + commands.append("no {0}".format(key)) + elif value == "default": + default_value = PARAM_TO_DEFAULT_KEYMAP.get(key) + existing_value = existing_commands.get(key) + + if default_value: + commands.append("{0} {1}".format(key, default_value)) + elif existing_value: + if key == "confederation peers": + existing_value = " ".join(existing_value) + commands.append("no {0} {1}".format(key, existing_value)) + elif not value: + existing_value = existing_commands.get(key) + if existing_value: + commands.append("no {0} {1}".format(key, existing_value)) + elif key == "confederation peers": + commands.append("{0} {1}".format(key, value)) + elif key.startswith("timers bgp"): + command = "timers bgp {0} {1}".format( + proposed["timer_bgp_keepalive"], + proposed["timer_bgp_hold"], + ) + if command not in commands: + commands.append(command) + else: + if value.startswith("size"): + value = value.replace("_", " ") + command = "{0} {1}".format(key, value) + commands.append(command) + + parents = [] + if commands: + commands = fix_commands(commands) + parents = ["router bgp {0}".format(module.params["asn"])] + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + elif proposed: + if module.params["vrf"] != "default": + commands.append("vrf {0}".format(module.params["vrf"])) + parents = ["router bgp {0}".format(module.params["asn"])] + else: + commands.append("router bgp {0}".format(module.params["asn"])) + + candidate.add(commands, parents=parents) + + +def state_absent(module, existing, candidate): + commands = [] + parents = [] + if module.params["vrf"] == "default": + commands.append("no router bgp {0}".format(module.params["asn"])) + elif existing.get("vrf") == module.params["vrf"]: + commands.append("no vrf {0}".format(module.params["vrf"])) + parents = ["router bgp {0}".format(module.params["asn"])] + + candidate.add(commands, parents=parents) + + +def fix_commands(commands): + local_as_command = "" + confederation_id_command = "" + confederation_peers_command = "" + + for command in commands: + if "local-as" in command: + local_as_command = command + elif "confederation identifier" in command: + confederation_id_command = command + elif "confederation peers" in command: + confederation_peers_command = command + + if local_as_command and confederation_id_command: + if "no" in confederation_id_command: + commands.pop(commands.index(local_as_command)) + commands.pop(commands.index(confederation_id_command)) + commands.append(confederation_id_command) + commands.append(local_as_command) + else: + commands.pop(commands.index(local_as_command)) + commands.pop(commands.index(confederation_id_command)) + commands.append(local_as_command) + commands.append(confederation_id_command) + + if confederation_peers_command and confederation_id_command: + if local_as_command: + if "no" in local_as_command: + commands.pop(commands.index(local_as_command)) + commands.pop(commands.index(confederation_id_command)) + commands.pop(commands.index(confederation_peers_command)) + commands.append(confederation_id_command) + commands.append(confederation_peers_command) + commands.append(local_as_command) + else: + commands.pop(commands.index(local_as_command)) + commands.pop(commands.index(confederation_id_command)) + commands.pop(commands.index(confederation_peers_command)) + commands.append(local_as_command) + commands.append(confederation_id_command) + commands.append(confederation_peers_command) + else: + commands.pop(commands.index(confederation_peers_command)) + commands.pop(commands.index(confederation_id_command)) + commands.append(confederation_id_command) + commands.append(confederation_peers_command) + + return commands + + +def main(): + argument_spec = dict( + asn=dict(required=True, type="str"), + vrf=dict(required=False, type="str", default="default"), + bestpath_always_compare_med=dict(required=False, type="bool"), + bestpath_aspath_multipath_relax=dict(required=False, type="bool"), + bestpath_compare_neighborid=dict(required=False, type="bool"), + bestpath_compare_routerid=dict(required=False, type="bool"), + bestpath_cost_community_ignore=dict(required=False, type="bool"), + bestpath_med_confed=dict(required=False, type="bool"), + bestpath_med_missing_as_worst=dict(required=False, type="bool"), + bestpath_med_non_deterministic=dict(required=False, type="bool"), + cluster_id=dict(required=False, type="str"), + confederation_id=dict(required=False, type="str"), + confederation_peers=dict(required=False, type="list", elements="str"), + disable_policy_batching=dict(required=False, type="bool"), + disable_policy_batching_ipv4_prefix_list=dict(required=False, type="str"), + disable_policy_batching_ipv6_prefix_list=dict(required=False, type="str"), + enforce_first_as=dict(required=False, type="bool"), + event_history_cli=dict( + required=False, + choices=[ + "true", + "false", + "default", + "size_small", + "size_medium", + "size_large", + "size_disable", + ], + ), + event_history_detail=dict( + required=False, + choices=[ + "true", + "false", + "default", + "size_small", + "size_medium", + "size_large", + "size_disable", + ], + ), + event_history_events=dict( + required=False, + choices=[ + "true", + "false", + "default", + "size_small", + "size_medium", + "size_large", + "size_disable", + ], + ), + event_history_periodic=dict( + required=False, + choices=[ + "true", + "false", + "default", + "size_small", + "size_medium", + "size_large", + "size_disable", + ], + ), + fast_external_fallover=dict(required=False, type="bool"), + flush_routes=dict(required=False, type="bool"), + graceful_restart=dict(required=False, type="bool"), + graceful_restart_helper=dict(required=False, type="bool"), + graceful_restart_timers_restart=dict(required=False, type="str"), + graceful_restart_timers_stalepath_time=dict(required=False, type="str"), + isolate=dict(required=False, type="bool"), + local_as=dict(required=False, type="str"), + log_neighbor_changes=dict(required=False, type="bool"), + maxas_limit=dict(required=False, type="str"), + neighbor_down_fib_accelerate=dict(required=False, type="bool"), + reconnect_interval=dict(required=False, type="str"), + router_id=dict(required=False, type="str"), + shutdown=dict(required=False, type="bool"), + suppress_fib_pending=dict(required=False, type="bool"), + timer_bestpath_limit=dict(required=False, type="str"), + timer_bgp_hold=dict(required=False, type="str"), + timer_bgp_keepalive=dict(required=False, type="str"), + state=dict(choices=["present", "absent"], default="present", required=False), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=[["timer_bgp_hold", "timer_bgp_keepalive"]], + supports_check_mode=True, + ) + + warnings = list() + result = dict(changed=False, warnings=warnings) + + state = module.params["state"] + + if module.params["vrf"] != "default": + for param in GLOBAL_PARAMS: + if module.params[param]: + module.fail_json( + msg='Global params can be modified only under "default" VRF.', + vrf=module.params["vrf"], + global_param=param, + ) + + args = PARAM_TO_COMMAND_KEYMAP.keys() + existing = get_existing(module, args, warnings) + + if existing.get("asn") and state == "present": + if existing.get("asn") != module.params["asn"]: + module.fail_json( + msg="Another BGP ASN already exists.", + proposed_asn=module.params["asn"], + existing_asn=existing.get("asn"), + ) + + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + proposed = {} + for key, value in proposed_args.items(): + if key not in ["asn", "vrf"]: + if str(value).lower() == "default": + value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default") + if key == "confederation_peers": + if value[0] == "default": + if existing.get(key): + proposed[key] = "default" + else: + v = set([int(i) for i in value]) + ex = set([int(i) for i in existing.get(key)]) + if v != ex: + proposed[key] = " ".join(str(s) for s in v) + else: + if existing.get(key) != value: + proposed[key] = value + + candidate = CustomNetworkConfig(indent=3) + if state == "present": + state_present(module, existing, proposed, candidate) + elif existing.get("asn") == module.params["asn"]: + state_absent(module, existing, candidate) + + if candidate: + candidate = candidate.items_text() + if not module.check_mode: + load_config(module, candidate) + result["changed"] = True + result["commands"] = candidate + else: + result["commands"] = [] + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py new file mode 100644 index 00000000..c838d25f --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py @@ -0,0 +1,1031 @@ +#!/usr/bin/python +# -*- 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 module file for nxos_bgp_address_family +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_bgp_address_family +short_description: BGP Address Family resource module. +description: +- This module manages BGP Address Family configuration on devices running Cisco NX-OS. +version_added: 2.0.0 +notes: +- Tested against NX-OS 9.3.6. +- Unsupported for Cisco MDS +- For managing BGP neighbor address family configurations please use + the M(cisco.nxos.nxos_bgp_neighbor_address_family) module. +- This module works with connection C(network_cli) and C(httpapi). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^router bgp'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of BGP process configuration. + type: dict + suboptions: + as_number: + description: Autonomous System Number of the router. + type: str + address_family: + description: Address Family related configurations. + type: list + elements: dict + suboptions: + afi: + description: Address Family indicator. + type: str + choices: ["ipv4", "ipv6", "link-state", "vpnv4", "vpnv6", "l2vpn"] + required: True + safi: + description: Sub Address Family indicator. + type: str + choices: ["unicast", "multicast", "mvpn", "evpn"] + additional_paths: + description: Additional paths configuration. + type: dict + suboptions: + install_backup: + description: Install backup path. + type: bool + receive: + description: Additional paths Receive capability. + type: bool + selection: + description: Additional paths selection + type: dict + suboptions: + route_map: + description: Route-map for additional paths selection + type: str + send: + description: Additional paths Send capability + type: bool + advertise_pip: + description: Advertise physical ip for type-5 route. + type: bool + advertise_l2vpn_evpn: + description: Enable advertising EVPN routes. + type: bool + advertise_system_mac: + description: Advertise extra EVPN RT-2 with system MAC. + type: bool + allow_vni_in_ethertag: + description: Allow VNI in Ethernet Tag field in EVPN route. + type: bool + aggregate_address: + description: Configure BGP aggregate prefixes + type: list + elements: dict + suboptions: + prefix: + description: Aggregate prefix. + type: str + advertise_map: + description: Select attribute information from specific routes. + type: str + as_set: + description: Generate AS-SET information. + type: bool + attribute_map: + description: Set attribute information of aggregate. + type: str + summary_only: + description: Do not advertise more specifics. + type: bool + suppress_map: + description: Conditionally filter more specific routes. + type: str + client_to_client: + description: Configure client-to-client route reflection. + type: dict + suboptions: + no_reflection: + description: Reflection of routes permitted. + type: bool + dampen_igp_metric: + description: Dampen IGP metric-related changes. + type: int + dampening: + description: Configure route flap dampening. + type: dict + suboptions: + set: + description: Set route flap dampening. + type: bool + decay_half_life: + description: Decay half life. + type: int + start_reuse_route: + description: Value to start reusing a route. + type: int + start_suppress_route: + description: Value to start suppressing a route. + type: int + max_suppress_time: + description: Maximum suppress time for stable route. + type: int + route_map: + description: Apply route-map to specify dampening criteria. + type: str + default_information: + description: Control distribution of default information. + type: dict + suboptions: + originate: + description: Distribute a default route. + type: bool + default_metric: + description: Set metric of redistributed routes. + type: int + distance: + description: Configure administrative distance. + type: dict + suboptions: + ebgp_routes: + description: Distance for EBGP routes. + type: int + ibgp_routes: + description: Distance for IBGP routes. + type: int + local_routes: + description: Distance for local routes. + type: int + export_gateway_ip: + description: Export Gateway IP to Type-5 EVPN routes for VRF + type: bool + inject_map: + description: Routemap which specifies prefixes to inject. + type: list + elements: dict + suboptions: + route_map: + description: Route-map name. + type: str + exist_map: + description: Routemap which specifies exist condition. + type: str + copy_attributes: + description: Copy attributes from aggregate. + type: bool + maximum_paths: + description: Forward packets over multipath paths. + type: dict + suboptions: + parallel_paths: + description: Number of parallel paths. + type: int + ibgp: + description: Configure multipath for IBGP paths. + type: dict + suboptions: + parallel_paths: + description: Number of parallel paths. + type: int + eibgp: + description: Configure multipath for both EBGP and IBGP paths. + type: dict + suboptions: + parallel_paths: + description: Number of parallel paths. + type: int + local: + description: Configure multipath for local paths. + type: dict + suboptions: + parallel_paths: + description: Number of parallel paths. + type: int + mixed: + description: Configure multipath for local and remote paths. + type: dict + suboptions: + parallel_paths: + description: Number of parallel paths. + type: int + networks: + description: Configure an IP prefix to advertise. + type: list + elements: dict + suboptions: + prefix: + description: IP prefix in CIDR format. + type: str + route_map: + description: Route-map name. + type: str + nexthop: + description: Nexthop tracking. + type: dict + suboptions: + route_map: + description: Route-map name. + type: str + trigger_delay: + description: Set the delay to trigger nexthop tracking. + type: dict + suboptions: + critical_delay: + description: + - Nexthop changes affecting reachability. + - Delay value (miliseconds). + type: int + non_critical_delay: + description: + - Other nexthop changes. + - Delay value (miliseconds). + type: int + redistribute: + description: Configure redistribution. + type: list + elements: dict + suboptions: + protocol: + description: + - The name of the protocol. + type: str + choices: ["am", "direct", "eigrp", "isis", "lisp", "ospf", "ospfv3", "rip", "static", "hmm"] + required: true + id: + description: + - The identifier for the protocol specified. + type: str + route_map: + description: + - The route map policy to constrain redistribution. + type: str + required: true + retain: + description: Retain the routes based on Target VPN Extended Communities. + type: dict + suboptions: + route_target: + description: Specify Target VPN Extended Communities + type: dict + suboptions: + retain_all: + description: All the routes regardless of Target-VPN community + type: bool + route_map: + description: Apply route-map to filter routes. + type: str + suppress_inactive: + description: Advertise only active routes to peers. + type: bool + table_map: + description: + - Policy for filtering/modifying OSPF routes before sending them to RIB. + type: dict + suboptions: + name: + description: + - The Route Map name. + type: str + required: true + filter: + description: + - Block the OSPF routes from being sent to RIB. + type: bool + timers: + description: Configure bgp related timers. + type: dict + suboptions: + bestpath_defer: + description: Configure bestpath defer timer value for batch prefix processing. + type: dict + suboptions: + defer_time: + description: Bestpath defer time (mseconds). + type: int + maximum_defer_time: + description: Maximum bestpath defer time (mseconds). + type: int + wait_igp_convergence: + description: Delay initial bestpath until redistributed IGPs have converged. + type: bool + vrf: + description: Virtual Router Context. + type: str + state: + description: + - The state the configuration should be left in. + - State I(deleted) only removes BGP attributes that this modules + manages and does not negate the BGP process completely. + - Refer to examples for more details. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# Nexus9000v# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_bgp_address_family: + config: + as_number: 65536 + address_family: + - afi: ipv4 + safi: multicast + networks: + - prefix: 192.0.2.32/27 + - prefix: 192.0.2.64/27 + route_map: rmap1 + nexthop: + route_map: rmap2 + trigger_delay: + critical_delay: 120 + non_critical_delay: 180 + - afi: ipv4 + safi: unicast + vrf: site-1 + default_information: + originate: True + aggregate_address: + - prefix: 203.0.113.0/24 + as_set: True + summary_only: True + - afi: ipv6 + safi: multicast + vrf: site-1 + redistribute: + - protocol: ospfv3 + id: 100 + route_map: rmap-ospf-1 + - protocol: eigrp + id: 101 + route_map: rmap-eigrp-1 + +# Task output +# ------------- +# before: {} +# +# commands: +# - router bgp 65536 +# - address-family ipv4 multicast +# - nexthop route-map rmap2 +# - nexthop trigger-delay critical 120 non-critical 180 +# - network 192.0.2.32/27 +# - network 192.0.2.64/27 route-map rmap1 +# - vrf site-1 +# - address-family ipv4 unicast +# - default-information originate +# - aggregate-address 203.0.113.0/24 as-set summary-only +# - address-family ipv6 multicast +# - redistribute ospfv3 100 route-map rmap-ospf-1 +# - redistribute eigrp 101 route-map rmap-eigrp-1 +# +# after: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: multicast +# networks: +# - prefix: 192.0.2.32/27 +# - prefix: 192.0.2.64/27 +# route_map: rmap1 +# nexthop: +# route_map: rmap2 +# trigger_delay: +# critical_delay: 120 +# non_critical_delay: 180 +# - afi: ipv4 +# safi: unicast +# vrf: site-1 +# default_information: +# originate: True +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True +# - afi: ipv6 +# safi: multicast +# vrf: site-1 +# redistribute: +# - id: "100" +# protocol: ospfv3 +# route_map: rmap-ospf-1 +# - id: "101" +# protocol: eigrp +# route_map: rmap-eigrp-1 + +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# address-family ipv4 multicast +# nexthop route-map rmap2 +# nexthop trigger-delay critical 120 non-critical 180 +# network 192.0.2.32/27 +# network 192.0.2.64/27 route-map rmap1 +# vrf site-1 +# address-family ipv4 unicast +# default-information originate +# aggregate-address 203.0.113.0/24 as-set summary-only +# address-family ipv6 multicast +# redistribute ospfv3 100 route-map rmap-ospf-1 +# redistribute eigrp 101 route-map rmap-eigrp-1 +# + +# Using replaced + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# address-family ipv4 multicast +# nexthop route-map rmap2 +# nexthop trigger-delay critical 120 non-critical 180 +# network 192.0.2.32/27 +# network 192.0.2.64/27 route-map rmap1 +# vrf site-1 +# address-family ipv4 unicast +# default-information originate +# aggregate-address 203.0.113.0/24 as-set summary-only +# address-family ipv6 multicast +# redistribute ospfv3 100 route-map rmap-ospf-1 +# redistribute eigrp 101 route-map rmap-eigrp-1 + +- name: Replace configuration of specified AFs + cisco.nxos.nxos_bgp_address_family: + config: + as_number: 65536 + address_family: + - afi: ipv4 + safi: multicast + networks: + - prefix: 192.0.2.64/27 + route_map: rmap1 + nexthop: + route_map: rmap2 + trigger_delay: + critical_delay: 120 + non_critical_delay: 180 + aggregate_address: + - prefix: 203.0.113.0/24 + as_set: True + summary_only: True + - afi: ipv4 + safi: unicast + vrf: site-1 + state: replaced + +# Task output +# ------------- +# before: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: multicast +# networks: +# - prefix: 192.0.2.32/27 +# - prefix: 192.0.2.64/27 +# route_map: rmap1 +# nexthop: +# route_map: rmap2 +# trigger_delay: +# critical_delay: 120 +# non_critical_delay: 180 +# - afi: ipv4 +# safi: unicast +# vrf: site-1 +# default_information: +# originate: True +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True +# - afi: ipv6 +# safi: multicast +# vrf: site-1 +# redistribute: +# - id: "100" +# protocol: ospfv3 +# route_map: rmap-ospf-1 +# - id: "101" +# protocol: eigrp +# route_map: rmap-eigrp-1 +# +# commands: +# - router bgp 65536 +# - address-family ipv4 multicast +# - no network 192.0.2.32/27 +# - aggregate-address 203.0.113.0/24 as-set summary-only +# - vrf site-1 +# - address-family ipv4 unicast +# - no default-information originate +# - no aggregate-address 203.0.113.0/24 as-set summary-only +# +# after: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: multicast +# networks: +# - prefix: 192.0.2.64/27 +# route_map: rmap1 +# nexthop: +# route_map: rmap2 +# trigger_delay: +# critical_delay: 120 +# non_critical_delay: 180 +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True +# +# - afi: ipv4 +# safi: unicast +# vrf: site-1 +# +# - afi: ipv6 +# safi: multicast +# vrf: site-1 +# redistribute: +# - protocol: ospfv3 +# id: "100" +# route_map: rmap-ospf-1 +# - protocol: eigrp +# id: "101" +# route_map: rmap-eigrp-1 + +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# address-family ipv4 multicast +# nexthop route-map rmap2 +# nexthop trigger-delay critical 120 non-critical 180 +# network 192.0.2.64/27 route-map rmap1 +# aggregate-address 203.0.113.0/24 as-set summary-only +# vrf site-1 +# address-family ipv4 unicast +# address-family ipv6 multicast +# redistribute ospfv3 100 route-map rmap-ospf-1 +# redistribute eigrp 101 route-map rmap-eigrp-1 + +# Using overridden + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# address-family ipv4 multicast +# nexthop route-map rmap2 +# nexthop trigger-delay critical 120 non-critical 180 +# network 192.0.2.32/27 +# network 192.0.2.64/27 route-map rmap1 +# vrf site-1 +# address-family ipv4 unicast +# default-information originate +# aggregate-address 203.0.113.0/24 as-set summary-only +# address-family ipv6 multicast +# redistribute ospfv3 100 route-map rmap-ospf-1 +# redistribute eigrp 101 route-map rmap-eigrp-1 + +- name: Override all BGP AF configuration with provided configuration + cisco.nxos.nxos_bgp_address_family: &overridden + config: + as_number: 65536 + address_family: + - afi: ipv4 + safi: multicast + networks: + - prefix: 192.0.2.64/27 + route_map: rmap1 + aggregate_address: + - prefix: 203.0.113.0/24 + as_set: True + summary_only: True + - afi: ipv4 + safi: unicast + vrf: site-1 + state: overridden + +# Task output +# ------------- +# before: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: multicast +# networks: +# - prefix: 192.0.2.32/27 +# - prefix: 192.0.2.64/27 +# route_map: rmap1 +# nexthop: +# route_map: rmap2 +# trigger_delay: +# critical_delay: 120 +# non_critical_delay: 180 +# - afi: ipv4 +# safi: unicast +# vrf: site-1 +# default_information: +# originate: True +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True +# - afi: ipv6 +# safi: multicast +# vrf: site-1 +# redistribute: +# - id: "100" +# protocol: ospfv3 +# route_map: rmap-ospf-1 +# - id: "101" +# protocol: eigrp +# route_map: rmap-eigrp-1 +# +# commands: +# - router bgp 65536 +# - vrf site-1 +# - no address-family ipv6 multicast +# - exit +# - address-family ipv4 multicast +# - no nexthop route-map rmap2 +# - no nexthop trigger-delay critical 120 non-critical 180 +# - aggregate-address 203.0.113.0/24 as-set summary-only +# - no network 192.0.2.32/27 +# - vrf site-1 +# - address-family ipv4 unicast +# - no default-information originate +# - no aggregate-address 203.0.113.0/24 as-set summary-only +# +# after: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: multicast +# networks: +# - prefix: 192.0.2.64/27 +# route_map: rmap1 +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True +# - afi: ipv4 +# safi: unicast +# vrf: site-1 + +# +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# address-family ipv4 multicast +# network 192.0.2.64/27 route-map rmap1 +# aggregate-address 203.0.113.0/24 as-set summary-only +# vrf site-1 +# address-family ipv4 unicast +# + +# Using deleted to remove specified AFs + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# address-family ipv4 multicast +# nexthop route-map rmap2 +# nexthop trigger-delay critical 120 non-critical 180 +# network 192.0.2.32/27 +# network 192.0.2.64/27 route-map rmap1 +# vrf site-1 +# address-family ipv4 unicast +# default-information originate +# aggregate-address 203.0.113.0/24 as-set summary-only +# address-family ipv6 multicast +# redistribute ospfv3 100 route-map rmap-ospf-1 +# redistribute eigrp 101 route-map rmap-eigrp-1 + +- name: Delete specified BGP AFs + cisco.nxos.nxos_bgp_address_family: + config: + as_number: 65536 + address_family: + - afi: ipv4 + safi: multicast + - vrf: site-1 + afi: ipv6 + safi: multicast + state: deleted + +# Task output +# ------------- +# before: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: multicast +# networks: +# - prefix: 192.0.2.32/27 +# - prefix: 192.0.2.64/27 +# route_map: rmap1 +# nexthop: +# route_map: rmap2 +# trigger_delay: +# critical_delay: 120 +# non_critical_delay: 180 +# - afi: ipv4 +# safi: unicast +# vrf: site-1 +# default_information: +# originate: True +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True +# - afi: ipv6 +# safi: multicast +# vrf: site-1 +# redistribute: +# - id: "100" +# protocol: ospfv3 +# route_map: rmap-ospf-1 +# - id: "101" +# protocol: eigrp +# route_map: rmap-eigrp-1 +# +# commands: +# - router bgp 65563 +# - no address-family ipv4 multicast +# - vrf site-1 +# - no address-family ipv6 multicast +# +# after: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: unicast +# vrf: site-1 +# default_information: +# originate: True +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True + +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# vrf site-1 +# address-family ipv4 unicast +# default-information originate +# aggregate-address 203.0.113.0/24 as-set summary-only + +# Using deleted to remove all BGP AFs + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# address-family ipv4 multicast +# nexthop route-map rmap2 +# nexthop trigger-delay critical 120 non-critical 180 +# network 192.0.2.32/27 +# network 192.0.2.64/27 route-map rmap1 +# vrf site-1 +# address-family ipv4 unicast +# default-information originate +# aggregate-address 203.0.113.0/24 as-set summary-only +# address-family ipv6 multicast +# redistribute ospfv3 100 route-map rmap-ospf-1 +# redistribute eigrp 101 route-map rmap-eigrp-1 + +- name: Delete all BGP AFs + cisco.nxos.nxos_bgp_address_family: + state: deleted + +# Task output +# ------------- +# before: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: multicast +# networks: +# - prefix: 192.0.2.32/27 +# - prefix: 192.0.2.64/27 +# route_map: rmap1 +# nexthop: +# route_map: rmap2 +# trigger_delay: +# critical_delay: 120 +# non_critical_delay: 180 +# - afi: ipv4 +# safi: unicast +# vrf: site-1 +# default_information: +# originate: True +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True +# - afi: ipv6 +# safi: multicast +# vrf: site-1 +# redistribute: +# - id: "100" +# protocol: ospfv3 +# route_map: rmap-ospf-1 +# - id: "101" +# protocol: eigrp +# route_map: rmap-eigrp-1 +# +# commands: +# - router bgp 65563 +# - no address-family ipv4 multicast +# - vrf site-1 +# - no address-family ipv4 unicast +# - no address-family ipv6 multicast +# +# after: +# as_number: "65536" + +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# Nexus9000v# + +# Using rendered + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_bgp_address_family: + config: + as_number: 65536 + address_family: + - afi: ipv4 + safi: multicast + networks: + - prefix: 192.0.2.32/27 + - prefix: 192.0.2.64/27 + route_map: rmap1 + nexthop: + route_map: rmap2 + trigger_delay: + critical_delay: 120 + non_critical_delay: 180 + - afi: ipv4 + safi: unicast + vrf: site-1 + default_information: + originate: True + aggregate_address: + - prefix: 203.0.113.0/24 + as_set: True + summary_only: True + - afi: ipv6 + safi: multicast + vrf: site-1 + redistribute: + - protocol: ospfv3 + id: 100 + route_map: rmap-ospf-1 + - protocol: eigrp + id: 101 + route_map: rmap-eigrp-1 + state: rendered + +# Task Output (redacted) +# ----------------------- +# rendered: +# - router bgp 65536 +# - address-family ipv4 multicast +# - nexthop route-map rmap2 +# - nexthop trigger-delay critical 120 non-critical 180 +# - network 192.0.2.32/27 +# - network 192.0.2.64/27 route-map rmap1 +# - vrf site-1 +# - address-family ipv4 unicast +# - default-information originate +# - aggregate-address 203.0.113.0/24 as-set summary-only +# - address-family ipv6 multicast +# - redistribute ospfv3 100 route-map rmap-ospf-1 +# - redistribute eigrp 101 route-map rmap-eigrp-1 + +# Using parsed + +# parsed.cfg +# ------------ +# router bgp 65536 +# address-family ipv4 multicast +# nexthop route-map rmap2 +# nexthop trigger-delay critical 120 non-critical 180 +# network 192.0.2.32/27 +# network 192.0.2.64/27 route-map rmap1 +# vrf site-1 +# address-family ipv4 unicast +# default-information originate +# aggregate-address 203.0.113.0/24 as-set summary-only +# address-family ipv6 multicast +# redistribute ospfv3 100 route-map rmap-ospf-1 +# redistribute eigrp 101 route-map rmap-eigrp-1 + +- name: Parse externally provided BGP AF config + cisco.nxos.nxos_bgp_address_family: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# as_number: "65536" +# address_family: +# - afi: ipv4 +# safi: multicast +# networks: +# - prefix: 192.0.2.32/27 +# - prefix: 192.0.2.64/27 +# route_map: rmap1 +# nexthop: +# route_map: rmap2 +# trigger_delay: +# critical_delay: 120 +# non_critical_delay: 180 +# - afi: ipv4 +# safi: unicast +# vrf: site-1 +# default_information: +# originate: True +# aggregate_address: +# - prefix: 203.0.113.0/24 +# as_set: True +# summary_only: True +# - afi: ipv6 +# safi: multicast +# vrf: site-1 +# redistribute: +# - id: "100" +# protocol: ospfv3 +# route_map: rmap-ospf-1 +# - id: "101" +# protocol: eigrp +# route_map: rmap-eigrp-1 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_address_family.bgp_address_family import ( + Bgp_address_familyArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_address_family.bgp_address_family import ( + Bgp_address_family, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Bgp_address_familyArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Bgp_address_family(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py new file mode 100644 index 00000000..290f241d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py @@ -0,0 +1,877 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_bgp_af +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2023-02-24) Manages BGP Address-family configuration. +description: +- Manages BGP Address-family configurations on NX-OS switches. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +deprecated: + alternative: nxos_bgp_address_family + why: Updated module released with more functionality. + removed_at_date: '2023-02-24' +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- C(state=absent) removes the whole BGP ASN configuration +- Default, where supported, restores params default value. +options: + asn: + description: + - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or + ASDOT notation. + required: true + type: str + vrf: + description: + - Name of the VRF. The name 'default' is a valid VRF representing the global bgp. + default: 'default' + type: str + afi: + description: + - Address Family Identifier. + required: true + choices: + - ipv4 + - ipv6 + - vpnv4 + - vpnv6 + - l2vpn + type: str + safi: + description: + - Sub Address Family Identifier. + required: true + choices: + - unicast + - multicast + - evpn + type: str + additional_paths_install: + description: + - Install a backup path into the forwarding table and provide prefix independent + convergence (PIC) in case of a PE-CE link failure. + type: bool + additional_paths_receive: + description: + - Enables the receive capability of additional paths for all of the neighbors + under this address family for which the capability has not been disabled. + type: bool + additional_paths_selection: + description: + - Configures the capability of selecting additional paths for a prefix. Valid + values are a string defining the name of the route-map. + type: str + additional_paths_send: + description: + - Enables the send capability of additional paths for all of the neighbors under + this address family for which the capability has not been disabled. + type: bool + advertise_l2vpn_evpn: + description: + - Advertise evpn routes. + type: bool + client_to_client: + description: + - Configure client-to-client route reflection. + type: bool + dampen_igp_metric: + description: + - Specify dampen value for IGP metric-related changes, in seconds. Valid values + are integer and keyword 'default'. + type: str + dampening_state: + description: + - Enable/disable route-flap dampening. + type: bool + dampening_half_time: + description: + - Specify decay half-life in minutes for route-flap dampening. Valid values are + integer and keyword 'default'. + type: str + dampening_max_suppress_time: + description: + - Specify max suppress time for route-flap dampening stable route. Valid values + are integer and keyword 'default'. + type: str + dampening_reuse_time: + description: + - Specify route reuse time for route-flap dampening. Valid values are integer + and keyword 'default'. + type: str + dampening_routemap: + description: + - Specify route-map for route-flap dampening. Valid values are a string defining + the name of the route-map. + type: str + dampening_suppress_time: + description: + - Specify route suppress time for route-flap dampening. Valid values are integer + and keyword 'default'. + type: str + default_information_originate: + description: + - Default information originate. + type: bool + default_metric: + description: + - Sets default metrics for routes redistributed into BGP. Valid values are Integer + or keyword 'default' + type: str + distance_ebgp: + description: + - Sets the administrative distance for eBGP routes. Valid values are Integer or + keyword 'default'. + type: str + distance_ibgp: + description: + - Sets the administrative distance for iBGP routes. Valid values are Integer or + keyword 'default'. + type: str + distance_local: + description: + - Sets the administrative distance for local BGP routes. Valid values are Integer + or keyword 'default'. + type: str + inject_map: + description: + - An array of route-map names which will specify prefixes to inject. Each array + entry must first specify the inject-map name, secondly an exist-map name, and + optionally the copy-attributes keyword which indicates that attributes should + be copied from the aggregate. For example [['lax_inject_map', 'lax_exist_map'], + ['nyc_inject_map', 'nyc_exist_map', 'copy-attributes'], ['fsd_inject_map', 'fsd_exist_map']]. + type: list + elements: list + maximum_paths: + description: + - Configures the maximum number of equal-cost paths for load sharing. Valid value + is an integer in the range 1-64. + type: str + maximum_paths_ibgp: + description: + - Configures the maximum number of ibgp equal-cost paths for load sharing. Valid + value is an integer in the range 1-64. + type: str + networks: + description: + - Networks to configure. Valid value is a list of network prefixes to advertise. + The list must be in the form of an array. Each entry in the array must include + a prefix address and an optional route-map. For example [['10.0.0.0/16', 'routemap_LA'], + ['192.168.1.1', 'Chicago'], ['192.168.2.0/24'], ['192.168.3.0/24', 'routemap_NYC']]. + type: list + elements: list + next_hop_route_map: + description: + - Configure a route-map for valid nexthops. Valid values are a string defining + the name of the route-map. + type: str + redistribute: + description: + - A list of redistribute directives. Multiple redistribute entries are allowed. + The list must be in the form of a nested array. the first entry of each array + defines the source-protocol to redistribute from; the second entry defines a + route-map name. A route-map is highly advised but may be optional on some platforms, + in which case it may be omitted from the array list. For example [['direct', + 'rm_direct'], ['lisp', 'rm_lisp']]. + type: list + elements: list + suppress_inactive: + description: + - Advertises only active routes to peers. + type: bool + table_map: + description: + - Apply table-map to filter routes downloaded into URIB. Valid values are a string. + type: str + table_map_filter: + description: + - Filters routes rejected by the route-map and does not download them to the RIB. + type: bool + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: + - present + - absent + type: str + retain_route_target: + description: + - Retains all of the routes or the routes which are part of configured route-map. + Valid values are route-map names or keyword C(all) or keyword C(default). C(all) + retains all the routes regardless of Target-VPN community. C(default) will disable + the retain route target option. If you are using route-map name please ensure + that the name is not same as C(all) and C(default). + type: str + version_added: 1.1.0 +""" +EXAMPLES = """ +# configure a simple address-family +- cisco.nxos.nxos_bgp_af: + asn: 65535 + vrf: TESTING + afi: ipv4 + safi: unicast + advertise_l2vpn_evpn: true + state: present + retain_route_target: all +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["router bgp 65535", "vrf TESTING", + "address-family ipv4 unicast", "advertise l2vpn evpn", + "retain route-target all"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +BOOL_PARAMS = [ + "additional_paths_install", + "additional_paths_receive", + "additional_paths_send", + "advertise_l2vpn_evpn", + "dampening_state", + "default_information_originate", + "suppress_inactive", +] +PARAM_TO_DEFAULT_KEYMAP = { + "maximum_paths": "1", + "maximum_paths_ibgp": "1", + "client_to_client": True, + "distance_ebgp": "20", + "distance_ibgp": "200", + "distance_local": "220", + "dampen_igp_metric": "600", +} +PARAM_TO_COMMAND_KEYMAP = { + "asn": "router bgp", + "afi": "address-family", + "safi": "address-family", + "additional_paths_install": "additional-paths install backup", + "additional_paths_receive": "additional-paths receive", + "additional_paths_selection": "additional-paths selection route-map", + "additional_paths_send": "additional-paths send", + "advertise_l2vpn_evpn": "advertise l2vpn evpn", + "client_to_client": "client-to-client reflection", + "dampen_igp_metric": "dampen-igp-metric", + "dampening_state": "dampening", + "dampening_half_time": "dampening", + "dampening_max_suppress_time": "dampening", + "dampening_reuse_time": "dampening", + "dampening_routemap": "dampening route-map", + "dampening_suppress_time": "dampening", + "default_information_originate": "default-information originate", + "default_metric": "default-metric", + "distance_ebgp": "distance", + "distance_ibgp": "distance", + "distance_local": "distance", + "inject_map": "inject-map", + "maximum_paths": "maximum-paths", + "maximum_paths_ibgp": "maximum-paths ibgp", + "networks": "network", + "redistribute": "redistribute", + "next_hop_route_map": "nexthop route-map", + "suppress_inactive": "suppress-inactive", + "table_map": "table-map", + "table_map_filter": "table-map-filter", + "vrf": "vrf", + "retain_route_target": "retain route-target", +} +DAMPENING_PARAMS = [ + "dampening_half_time", + "dampening_suppress_time", + "dampening_reuse_time", + "dampening_max_suppress_time", +] + + +def get_value(arg, config, module): + command = PARAM_TO_COMMAND_KEYMAP[arg] + command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M) + has_command_val = command_val_re.search(config) + + if arg in ["networks", "redistribute", "inject_map"]: + value = [] + for ele in command_val_re.findall(config): + tl = ele.split() + if "exist-map" in tl: + tl.remove("exist-map") + elif "route-map" in tl: + tl.remove("route-map") + value.append(tl) + + elif command == "distance": + distance_re = r".*distance\s(?P<d_ebgp>\w+)\s(?P<d_ibgp>\w+)\s(?P<d_local>\w+)" + match_distance = re.match(distance_re, config, re.DOTALL) + + value = "" + if match_distance: + distance_group = match_distance.groupdict() + + if arg == "distance_ebgp": + value = distance_group["d_ebgp"] + elif arg == "distance_ibgp": + value = distance_group["d_ibgp"] + elif arg == "distance_local": + value = distance_group["d_local"] + + elif command.split()[0] == "dampening": + value = "" + if arg == "dampen_igp_metric" or arg == "dampening_routemap": + if command in config: + value = has_command_val.group("value") + else: + dampening_re = r".*dampening\s(?P<half>\w+)\s(?P<reuse>\w+)\s(?P<suppress>\w+)\s(?P<max_suppress>\w+)" + match_dampening = re.match(dampening_re, config, re.DOTALL) + if match_dampening: + dampening_group = match_dampening.groupdict() + + if arg == "dampening_half_time": + value = dampening_group["half"] + elif arg == "dampening_reuse_time": + value = dampening_group["reuse"] + elif arg == "dampening_suppress_time": + value = dampening_group["suppress"] + elif arg == "dampening_max_suppress_time": + value = dampening_group["max_suppress"] + else: + if arg == "dampening_state": + value = True if "dampening" in config else False + elif arg == "table_map_filter": + tmf_regex = re.compile(r"\s+table-map.*filter$", re.M) + value = False + if tmf_regex.search(config): + value = True + + elif arg == "table_map": + tm_regex = re.compile(r"(?:table-map\s)(?P<value>\S+)(\sfilter)?$", re.M) + has_tablemap = tm_regex.search(config) + value = "" + if has_tablemap: + value = has_tablemap.group("value") + + elif arg == "client_to_client": + no_command_re = re.compile(r"^\s+no\s{0}\s*$".format(command), re.M) + value = True + + if no_command_re.search(config): + value = False + + elif arg == "retain_route_target": + value = "" + if command in config: + route_target = has_command_val.group("value") + if route_target: + if route_target == "all": + value = "all" + elif "route-map" in route_target: + value = route_target.replace("route-map ", "") + + elif arg in BOOL_PARAMS: + command_re = re.compile(r"^\s+{0}\s*$".format(command), re.M) + value = False + + if command_re.search(config): + value = True + + else: + value = "" + + if has_command_val: + value = has_command_val.group("value") + + return value + + +def get_existing(module, args, warnings): + existing = {} + netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) + + asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.DOTALL) + match_asn = asn_regex.match(str(netcfg)) + + if match_asn: + existing_asn = match_asn.group("existing_asn") + parents = ["router bgp {0}".format(existing_asn)] + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + parents.append("address-family {0} {1}".format(module.params["afi"], module.params["safi"])) + config = netcfg.get_section(parents) + + if config: + for arg in args: + if arg not in ["asn", "afi", "safi", "vrf"]: + gv = get_value(arg, config, module) + if gv: + existing[arg] = gv + else: + if arg != "client_to_client" and arg in PARAM_TO_DEFAULT_KEYMAP.keys(): + existing[arg] = PARAM_TO_DEFAULT_KEYMAP.get(arg) + else: + existing[arg] = gv + + existing["asn"] = existing_asn + existing["afi"] = module.params["afi"] + existing["safi"] = module.params["safi"] + existing["vrf"] = module.params["vrf"] + else: + warnings.append( + "The BGP process {0} didn't exist but the task just created it.".format( + module.params["asn"], + ), + ) + + return existing + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if new_key: + new_dict[new_key] = value + + return new_dict + + +def fix_proposed(module, proposed, existing): + commands = list() + command = "" + fixed_proposed = {} + for key, value in proposed.items(): + if key in DAMPENING_PARAMS: + if value != "default": + command = "dampening {0} {1} {2} {3}".format( + proposed.get("dampening_half_time"), + proposed.get("dampening_reuse_time"), + proposed.get("dampening_suppress_time"), + proposed.get("dampening_max_suppress_time"), + ) + else: + if existing.get(key): + command = "no dampening {0} {1} {2} {3}".format( + existing["dampening_half_time"], + existing["dampening_reuse_time"], + existing["dampening_suppress_time"], + existing["dampening_max_suppress_time"], + ) + if "default" in command: + command = "" + elif key.startswith("distance"): + command = "distance {0} {1} {2}".format( + proposed.get("distance_ebgp"), + proposed.get("distance_ibgp"), + proposed.get("distance_local"), + ) + else: + fixed_proposed[key] = value + + if command: + if command not in commands: + commands.append(command) + + return fixed_proposed, commands + + +def default_existing(existing_value, key, value): + commands = [] + if key == "network": + for network in existing_value: + if len(network) == 2: + commands.append("no network {0} route-map {1}".format(network[0], network[1])) + elif len(network) == 1: + commands.append("no network {0}".format(network[0])) + + elif key == "inject-map": + for maps in existing_value: + if len(maps) == 2: + commands.append("no inject-map {0} exist-map {1}".format(maps[0], maps[1])) + elif len(maps) == 3: + commands.append( + "no inject-map {0} exist-map {1} " "copy-attributes".format(maps[0], maps[1]), + ) + + elif key == "redistribute": + for maps in existing_value: + commands.append("no redistribute {0} route-map {1}".format(maps[0], maps[1])) + + elif key == "retain route-target": + if existing_value == "all": + commands.append("no {0} {1}".format(key, existing_value)) + elif existing_value != "default": + commands.append("no {0} route-map {1}".format(key, existing_value)) + + else: + commands.append("no {0} {1}".format(key, existing_value)) + return commands + + +def get_network_command(existing, key, value): + commands = [] + existing_networks = existing.get("networks", []) + for inet in value: + if not isinstance(inet, list): + inet = [inet] + if inet not in existing_networks: + if len(inet) == 1: + command = "{0} {1}".format(key, inet[0]) + elif len(inet) == 2: + command = "{0} {1} route-map {2}".format(key, inet[0], inet[1]) + if command: + commands.append(command) + for enet in existing_networks: + if enet not in value: + if len(enet) == 1: + command = "no {0} {1}".format(key, enet[0]) + elif len(enet) == 2: + command = "no {0} {1} route-map {2}".format(key, enet[0], enet[1]) + if command: + commands.append(command) + return commands + + +def get_inject_map_command(existing, key, value): + commands = [] + existing_maps = existing.get("inject_map", []) + for maps in value: + if not isinstance(maps, list): + maps = [maps] + if maps not in existing_maps: + if len(maps) == 2: + command = "inject-map {0} exist-map {1}".format(maps[0], maps[1]) + elif len(maps) == 3: + command = "inject-map {0} exist-map {1} " "copy-attributes".format(maps[0], maps[1]) + if command: + commands.append(command) + for emaps in existing_maps: + if emaps not in value: + if len(emaps) == 2: + command = "no inject-map {0} exist-map {1}".format(emaps[0], emaps[1]) + elif len(emaps) == 3: + command = "no inject-map {0} exist-map {1} " "copy-attributes".format( + emaps[0], + emaps[1], + ) + if command: + commands.append(command) + return commands + + +def get_redistribute_command(existing, key, value): + commands = [] + existing_rules = existing.get("redistribute", []) + for rule in value: + if not isinstance(rule, list): + rule = [rule] + if rule not in existing_rules: + command = "redistribute {0} route-map {1}".format(rule[0], rule[1]) + commands.append(command) + for erule in existing_rules: + if erule not in value: + command = "no redistribute {0} route-map {1}".format(erule[0], erule[1]) + commands.append(command) + return commands + + +def get_table_map_command(module, existing, key, value): + commands = [] + if key == "table-map": + if value != "default": + command = "{0} {1}".format(key, module.params["table_map"]) + if ( + module.params["table_map_filter"] is not None + and module.params["table_map_filter"] != "default" + ): + command += " filter" + commands.append(command) + else: + if existing.get("table_map"): + command = "no {0} {1}".format(key, existing.get("table_map")) + commands.append(command) + return commands + + +def get_retain_route_target_command(existing, key, value): + commands = [] + if key == "retain route-target": + if value != "default": + if value == "all": + command = "{0} {1}".format(key, value) + else: + command = "{0} route-map {1}".format(key, value) + else: + existing_value = existing.get("retain_route_target") + if existing_value == "all": + command = "no {0} {1}".format(key, existing_value) + else: + command = "no {0} route-map {1}".format(key, existing_value) + commands.append(command) + return commands + + +def get_default_table_map_filter(existing): + commands = [] + existing_table_map_filter = existing.get("table_map_filter") + if existing_table_map_filter: + existing_table_map = existing.get("table_map") + if existing_table_map: + command = "table-map {0}".format(existing_table_map) + commands.append(command) + return commands + + +def state_present(module, existing, proposed, candidate): + fixed_proposed, commands = fix_proposed(module, proposed, existing) + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, fixed_proposed) + existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) + for key, value in proposed_commands.items(): + if key == "address-family": + addr_family_command = "address-family {0} {1}".format( + module.params["afi"], + module.params["safi"], + ) + if addr_family_command not in commands: + commands.append(addr_family_command) + + elif key.startswith("table-map"): + table_map_commands = get_table_map_command(module, existing, key, value) + if table_map_commands: + commands.extend(table_map_commands) + + elif value is True: + commands.append(key) + + elif value is False: + commands.append("no {0}".format(key)) + + elif value == "default": + if key in PARAM_TO_DEFAULT_KEYMAP: + commands.append("{0} {1}".format(key, PARAM_TO_DEFAULT_KEYMAP[key])) + + elif existing_commands.get(key): + if key == "table-map-filter": + default_tmf_command = get_default_table_map_filter(existing) + + if default_tmf_command: + commands.extend(default_tmf_command) + else: + existing_value = existing_commands.get(key) + default_command = default_existing(existing_value, key, value) + if default_command: + commands.extend(default_command) + else: + if key == "network": + network_commands = get_network_command(existing, key, value) + if network_commands: + commands.extend(network_commands) + + elif key == "inject-map": + inject_map_commands = get_inject_map_command(existing, key, value) + if inject_map_commands: + commands.extend(inject_map_commands) + + elif key == "redistribute": + redistribute_commands = get_redistribute_command(existing, key, value) + if redistribute_commands: + commands.extend(redistribute_commands) + + elif key == "retain route-target": + retain_route_target_commands = get_retain_route_target_command(existing, key, value) + if retain_route_target_commands: + commands.extend(retain_route_target_commands) + + else: + command = "{0} {1}".format(key, value) + commands.append(command) + + if commands: + parents = ["router bgp {0}".format(module.params["asn"])] + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + addr_family_command = "address-family {0} {1}".format( + module.params["afi"], + module.params["safi"], + ) + parents.append(addr_family_command) + if addr_family_command in commands: + commands.remove(addr_family_command) + candidate.add(commands, parents=parents) + + +def state_absent(module, candidate): + commands = [] + parents = ["router bgp {0}".format(module.params["asn"])] + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + commands.append("no address-family {0} {1}".format(module.params["afi"], module.params["safi"])) + candidate.add(commands, parents=parents) + + +def main(): + argument_spec = dict( + asn=dict(required=True, type="str"), + vrf=dict(required=False, type="str", default="default"), + safi=dict(required=True, type="str", choices=["unicast", "multicast", "evpn"]), + afi=dict( + required=True, + type="str", + choices=["ipv4", "ipv6", "vpnv4", "vpnv6", "l2vpn"], + ), + additional_paths_install=dict(required=False, type="bool"), + additional_paths_receive=dict(required=False, type="bool"), + additional_paths_selection=dict(required=False, type="str"), + additional_paths_send=dict(required=False, type="bool"), + advertise_l2vpn_evpn=dict(required=False, type="bool"), + client_to_client=dict(required=False, type="bool"), + dampen_igp_metric=dict(required=False, type="str"), + dampening_state=dict(required=False, type="bool"), + dampening_half_time=dict(required=False, type="str"), + dampening_max_suppress_time=dict(required=False, type="str"), + dampening_reuse_time=dict(required=False, type="str"), + dampening_routemap=dict(required=False, type="str"), + dampening_suppress_time=dict(required=False, type="str"), + default_information_originate=dict(required=False, type="bool"), + default_metric=dict(required=False, type="str"), + distance_ebgp=dict(required=False, type="str"), + distance_ibgp=dict(required=False, type="str"), + distance_local=dict(required=False, type="str"), + inject_map=dict(required=False, type="list", elements="list"), + maximum_paths=dict(required=False, type="str"), + maximum_paths_ibgp=dict(required=False, type="str"), + networks=dict(required=False, type="list", elements="list"), + next_hop_route_map=dict(required=False, type="str"), + redistribute=dict(required=False, type="list", elements="list"), + suppress_inactive=dict(required=False, type="bool"), + table_map=dict(required=False, type="str"), + table_map_filter=dict(required=False, type="bool"), + state=dict(choices=["present", "absent"], default="present", required=False), + retain_route_target=dict(required=False, type="str"), + ) + + mutually_exclusive = [ + ("dampening_state", "dampening_routemap"), + ("dampening_state", "dampening_half_time"), + ("dampening_state", "dampening_suppress_time"), + ("dampening_state", "dampening_reuse_time"), + ("dampening_state", "dampening_max_suppress_time"), + ("dampening_routemap", "dampening_half_time"), + ("dampening_routemap", "dampening_suppress_time"), + ("dampening_routemap", "dampening_reuse_time"), + ("dampening_routemap", "dampening_max_suppress_time"), + ] + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_together=[ + DAMPENING_PARAMS, + ["distance_ibgp", "distance_ebgp", "distance_local"], + ], + supports_check_mode=True, + ) + + warnings = list() + result = dict(changed=False, warnings=warnings) + + state = module.params["state"] + + if module.params["advertise_l2vpn_evpn"]: + if module.params["vrf"] == "default": + module.fail_json( + msg="It is not possible to advertise L2VPN " + "EVPN in the default VRF. Please specify " + "another one.", + vrf=module.params["vrf"], + ) + + if module.params["table_map_filter"] and not module.params["table_map"]: + module.fail_json(msg="table_map param is needed when using" " table_map_filter filter.") + + args = PARAM_TO_COMMAND_KEYMAP.keys() + existing = get_existing(module, args, warnings) + + if existing.get("asn") and state == "present": + if existing.get("asn") != module.params["asn"]: + module.fail_json( + msg="Another BGP ASN already exists.", + proposed_asn=module.params["asn"], + existing_asn=existing.get("asn"), + ) + + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + + for arg in ["networks", "inject_map", "redistribute"]: + if proposed_args.get(arg): + if proposed_args[arg][0] == "default": + proposed_args[arg] = "default" + + proposed = {} + for key, value in proposed_args.items(): + if key not in ["asn", "vrf"]: + if str(value).lower() == "default": + value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default") + if existing.get(key) != value: + proposed[key] = value + + candidate = CustomNetworkConfig(indent=3) + if state == "present": + state_present(module, existing, proposed, candidate) + elif state == "absent" and existing: + state_absent(module, candidate) + + if candidate: + candidate = candidate.items_text() + if not module.check_mode: + load_config(module, candidate) + result["changed"] = True + result["commands"] = candidate + else: + result["commands"] = [] + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py new file mode 100644 index 00000000..b3072d66 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py @@ -0,0 +1,1694 @@ +#!/usr/bin/python +# -*- 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 module file for nxos_bgp_global +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_bgp_global +short_description: BGP Global resource module. +description: +- This module manages global BGP configuration on devices running Cisco NX-OS. +version_added: 1.4.0 +notes: +- Tested against NX-OS 9.3.6. +- Unsupported for Cisco MDS +- This module works with connection C(network_cli) and C(httpapi). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^router bgp'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of BGP process configuration. + type: dict + suboptions: + as_number: + description: Autonomous System Number of the router. + type: str + affinity_group: + description: Configure an affinity group. + type: dict + suboptions: + group_id: + description: Affinity Group ID. + type: int + bestpath: &bestpath + description: Define the default bestpath selection algorithm. + type: dict + suboptions: + always_compare_med: + description: Compare MED on paths from different AS. + type: bool + as_path: + description: AS-Path. + type: dict + suboptions: + ignore: + description: Ignore AS-Path during bestpath selection. + type: bool + multipath_relax: + description: Relax AS-Path restriction when choosing multipaths. + type: bool + compare_neighborid: + description: When more paths are available than max path config, use neighborid as tie-breaker. + type: bool + compare_routerid: + description: Compare router-id for identical EBGP paths. + type: bool + cost_community_ignore: + description: Ignore cost communities in bestpath selection. + type: bool + igp_metric_ignore: + description: Ignore IGP metric for next-hop during bestpath selection. + type: bool + med: + description: MED + type: dict + suboptions: + confed: + description: Compare MED only from paths originated from within a confederation. + type: bool + missing_as_worst: + description: Treat missing MED as highest MED. + type: bool + non_deterministic: + description: Not always pick the best-MED path among paths from same AS. + type: bool + cluster_id: &cluster_id + description: Configure Route Reflector Cluster-ID. + type: str + confederation: &confederation + description: AS confederation parameters. + type: dict + suboptions: + identifier: + description: Set routing domain confederation AS. + type: str + peers: + description: Peer ASs in BGP confederation. + type: list + elements: str + disable_policy_batching: + description: Disable batching evaluation of outbound policy for a peer. + type: dict + suboptions: + set: + description: Set policy batching. + type: bool + ipv4: + description: IPv4 address-family settings. + type: dict + suboptions: + prefix_list: + description: Name of prefix-list to apply. + type: str + ipv6: + description: IPv6 address-family settings. + type: dict + suboptions: + prefix_list: + description: Name of prefix-list to apply. + type: str + nexthop: + description: Batching based on nexthop. + type: bool + dynamic_med_interval: + description: Sets the interval for dampening of med changes. + type: int + enforce_first_as: + description: Enforce neighbor AS is the first AS in AS-PATH attribute (EBGP). + type: bool + enhanced_error: + description: Enable BGP Enhanced error handling. + type: bool + fabric_soo: + description: Fabric site of origin. + type: str + fast_external_fallover: + description: Immediately reset the session if the link to a directly connected BGP peer goes down. + type: bool + flush_routes: + description: Flush routes in RIB upon controlled restart. + type: bool + graceful_restart: &graceful_restart + description: Configure Graceful Restart functionality. + type: dict + suboptions: + set: + description: Enable graceful-restart. + type: bool + restart_time: + description: Maximum time for restart advertised to peers. + type: int + stalepath_time: + description: Maximum time to keep a restarting peer's stale routes. + type: int + helper: + description: Configure Graceful Restart Helper mode functionality. + type: bool + graceful_shutdown: + description: Graceful-shutdown for BGP protocol. + type: dict + suboptions: + activate: + description: Send graceful-shutdown community on all routes. + type: dict + suboptions: + set: + description: Activiate graceful-shutdown. + type: bool + route_map: + description: Apply route-map to modify attributes for outbound. + type: str + aware: + description: Lower preference of routes carrying graceful-shutdown community. + type: bool + isolate: + description: Isolate this router from BGP perspective. + type: dict + suboptions: + set: + description: Withdraw remote BGP routes to isolate this router. + type: bool + include_local: + description: Withdraw both local and remote BGP routes. + type: bool + log_neighbor_changes: &log_nbr + description: Log a message for neighbor up/down event. + type: bool + maxas_limit: &maxas_limit + description: Allow AS-PATH attribute from EBGP neighbor imposing a limit on number of ASes. + type: int + neighbors: &nbr + description: Configure BGP neighbors. + type: list + elements: dict + suboptions: + neighbor_address: + description: IP address/Prefix of the neighbor or interface. + type: str + required: True + bfd: + description: Bidirectional Fast Detection for the neighbor. + type: dict + suboptions: + set: + description: Set BFD for this neighbor. + type: bool + singlehop: + description: Single-hop session. + type: bool + multihop: + description: Multihop session. + type: dict + suboptions: + set: + description: Set BFD multihop. + type: bool + interval: + description: Configure BFD session interval parameters. + type: dict + suboptions: + tx_interval: + description: TX interval in milliseconds. + type: int + min_rx_interval: + description: Minimum RX interval. + type: int + multiplier: + description: Detect Multiplier. + type: int + neighbor_affinity_group: + description: Configure an affinity group. + type: dict + suboptions: + group_id: + description: Affinity Group ID. + type: int + bmp_activate_server: + description: Specify server ID for activating BMP monitoring for the peer. + type: int + capability: + description: Capability. + type: dict + suboptions: + suppress_4_byte_as: + description: Suppress 4-byte AS Capability. + type: bool + description: + description: Neighbor specific descripion. + type: str + disable_connected_check: + description: Disable check for directly connected peer. + type: bool + dont_capability_negotiate: + description: Don't negotiate capability with this neighbor. + type: bool + dscp: + description: Set dscp value for tcp transport. + type: str + dynamic_capability: + description: Dynamic Capability + type: bool + ebgp_multihop: + description: Specify multihop TTL for remote peer. + type: int + graceful_shutdown: + description: Graceful-shutdown for this neighbor. + type: dict + suboptions: + activate: + description: Send graceful-shutdown community. + type: dict + suboptions: + set: + description: Set activate. + type: bool + route_map: + description: Apply route-map to modify attributes for outbound. + type: str + inherit: + description: Inherit a template. + type: dict + suboptions: + peer: + description: Peer template to inherit. + type: str + peer_session: + description: Peer-session template to inherit. + type: str + local_as: + description: Specify the local-as number for the eBGP neighbor. + type: str + log_neighbor_changes: + description: Log message for neighbor up/down event. + type: dict + suboptions: + set: + description: + - Set log-neighbor-changes. + type: bool + disable: + description: + - Disable logging of neighbor up/down event. + type: bool + low_memory: + description: Behaviour in low memory situations. + type: dict + suboptions: + exempt: + description: Do not shutdown this peer when under memory pressure. + type: bool + password: + description: Configure a password for neighbor. + type: dict + suboptions: + encryption: + description: + - 0 specifies an UNENCRYPTED neighbor password. + - 3 specifies an 3DES ENCRYPTED neighbor password will follow. + - 7 specifies a Cisco type 7 ENCRYPTED neighbor password will follow. + type: int + key: + description: Authentication password. + type: str + path_attribute: + description: BGP path attribute optional filtering. + type: list + elements: dict + suboptions: + action: + description: Action. + type: str + choices: ["discard", "treat-as-withdraw"] + type: + description: Path attribute type + type: int + range: + description: Path attribute range. + type: dict + suboptions: + start: + description: Path attribute range start value. + type: int + end: + description: Path attribute range end value. + type: int + peer_type: + description: Neighbor facing + type: str + choices: ["fabric-border-leaf", "fabric-external"] + remote_as: + description: Specify Autonomous System Number of the neighbor. + type: str + remove_private_as: + description: Remove private AS number from outbound updates. + type: dict + suboptions: + set: + description: Remove private AS. + type: bool + replace_as: + description: Replace. + type: bool + all: + description: All. + type: bool + shutdown: + description: Administratively shutdown this neighbor. + type: bool + timers: + description: Configure keepalive and hold timers. + type: dict + suboptions: + keepalive: + description: Keepalive interval (seconds). + type: int + holdtime: + description: Holdtime (seconds). + type: int + transport: + description: BGP transport connection. + type: dict + suboptions: + connection_mode: + description: Specify type of connection. + type: dict + suboptions: + passive: + description: Allow passive connection setup only. + type: bool + ttl_security: + description: Enable TTL Security Mechanism. + type: dict + suboptions: + hops: + description: Specify hop count for remote peer. + type: int + update_source: + description: Specify source of BGP session and updates. + type: str + neighbor_down: &nbr_down + description: Handle BGP neighbor down event, due to various reasons. + type: dict + suboptions: + fib_accelerate: + description: Accelerate the hardware updates for IP/IPv6 adjacencies for neighbor. + type: bool + nexthop: + description: Nexthop resolution options. + type: dict + suboptions: + suppress_default_resolution: + description: Prohibit use of default route for nexthop address resolution. + type: bool + rd: + description: Secondary Route Distinguisher for vxlan multisite border gateway. + type: dict + suboptions: + dual: + description: Generate Secondary RD for all VRFs and L2VNIs. + type: bool + id: + description: Specify 2 byte value for ID. + type: int + reconnect_interval: &reconn_intv + description: Configure connection reconnect interval. + type: int + router_id: &rtr_id + description: Specify the IP address to use as router-id. + type: str + shutdown: &shtdwn + description: Administratively shutdown BGP protocol. + type: bool + suppress_fib_pending: &suppr + description: Advertise only routes that are programmed in hardware to peers. + type: bool + timers: &timers + description: Configure bgp related timers. + type: dict + suboptions: + bestpath_limit: + description: Configure timeout for first bestpath after restart. + type: dict + suboptions: + timeout: + description: Bestpath timeout (seconds). + type: int + always: + description: Configure update-delay-always option. + type: bool + bgp: + description: Configure different bgp keepalive and holdtimes. + type: dict + suboptions: + keepalive: + description: Keepalive interval (seconds). + type: int + holdtime: + description: Holdtime (seconds). + type: int + prefix_peer_timeout: + description: Prefix Peer timeout (seconds). + type: int + prefix_peer_wait: + description: Configure wait timer for a prefix peer. + type: int + vrfs: + description: Virtual Router Context configurations. + type: list + elements: dict + suboptions: + vrf: + description: VRF name. + type: str + allocate_index: + description: Configure allocate-index. + type: int + bestpath: *bestpath + cluster_id: *cluster_id + confederation: *confederation + graceful_restart: *graceful_restart + local_as: + description: Specify the local-as for this vrf. + type: str + log_neighbor_changes: *log_nbr + maxas_limit: *maxas_limit + neighbors: *nbr + neighbor_down: *nbr_down + reconnect_interval: *reconn_intv + router_id: *rtr_id + timers: *timers + state: + description: + - The state the configuration should be left in. + - State I(purged) removes all the BGP configurations from the + target device. Use caution with this state. + - State I(deleted) only removes BGP attributes that this modules + manages and does not negate the BGP process completely. Thereby, preserving + address-family related configurations under BGP context. + - Running states I(deleted) and I(replaced) will result in an error if there + are address-family configuration lines present under a neighbor, + or a vrf context that is to be removed. Please use the + M(cisco.nxos.nxos_bgp_af) or M(cisco.nxos.nxos_bgp_neighbor_af) + modules for prior cleanup. + - States I(merged) and I(replaced) will result in a failure if BGP is already configured + with a different ASN than what is provided in the task. In such cases, please use + state I(purged) to remove the existing BGP process and proceed further. + - Refer to examples for more details. + type: str + choices: + - merged + - replaced + - deleted + - purged + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# Nexus9000v# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_bgp_global: + config: + as_number: 65563 + router_id: 192.168.1.1 + bestpath: + as_path: + multipath_relax: True + compare_neighborid: True + cost_community_ignore: True + confederation: + identifier: 42 + peers: + - 65020 + - 65030 + - 65040 + log_neighbor_changes: True + maxas_limit: 20 + neighbors: + - neighbor_address: 192.168.1.100 + neighbor_affinity_group: + group_id: 160 + bmp_activate_server: 1 + remote_as: 65563 + description: NBR-1 + low_memory: + exempt: True + - neighbor_address: 192.168.1.101 + remote_as: 65563 + password: + encryption: 7 + key: 12090404011C03162E + neighbor_down: + fib_accelerate: True + vrfs: + - vrf: site-1 + allocate_index: 5000 + local_as: 200 + log_neighbor_changes: True + neighbors: + - neighbor_address: 198.51.100.1 + description: site-1-nbr-1 + password: + encryption: 3 + key: 13D4D3549493D2877B1DC116EE27A6BE + remote_as: 65562 + - neighbor_address: 198.51.100.2 + remote_as: 65562 + description: site-1-nbr-2 + - vrf: site-2 + local_as: 300 + log_neighbor_changes: True + neighbors: + - neighbor_address: 203.0.113.2 + description: site-2-nbr-1 + password: + encryption: 3 + key: AF92F4C16A0A0EC5BDF56CF58BC030F6 + remote_as: 65568 + neighbor_down: + fib_accelerate: True + +# Task output +# ------------- +# before: {} +# +# commands: +# - router bgp 65563 +# - bestpath as-path multipath-relax +# - bestpath compare-neighborid +# - bestpath cost-community ignore +# - confederation identifier 42 +# - log-neighbor-changes +# - maxas-limit 20 +# - neighbor-down fib-accelerate +# - router-id 192.168.1.1 +# - confederation peers 65020 65030 65040 +# - neighbor 192.168.1.100 +# - remote-as 65563 +# - affinity-group 160 +# - bmp-activate-server 1 +# - description NBR-1 +# - low-memory exempt +# - neighbor 192.168.1.101 +# - remote-as 65563 +# - password 7 12090404011C03162E +# - vrf site-1 +# - allocate-index 5000 +# - local-as 200 +# - log-neighbor-changes +# - neighbor 198.51.100.1 +# - remote-as 65562 +# - description site-1-nbr-1 +# - password 3 13D4D3549493D2877B1DC116EE27A6BE +# - neighbor 198.51.100.2 +# - remote-as 65562 +# - description site-1-nbr-2 +# - vrf site-2 +# - local-as 300 +# - log-neighbor-changes +# - neighbor-down fib-accelerate +# - neighbor 203.0.113.2 +# - remote-as 65568 +# - description site-2-nbr-1 +# - password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6 +# +# after: +# as_number: '65563' +# bestpath: +# as_path: +# multipath_relax: true +# compare_neighborid: true +# cost_community_ignore: true +# confederation: +# identifier: '42' +# peers: +# - '65020' +# - '65030' +# - '65040' +# log_neighbor_changes: true +# maxas_limit: 20 +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - bmp_activate_server: 1 +# description: NBR-1 +# low_memory: +# exempt: true +# neighbor_address: 192.168.1.100 +# neighbor_affinity_group: +# group_id: 160 +# remote_as: '65563' +# - neighbor_address: 192.168.1.101 +# password: +# encryption: 7 +# key: 12090404011C03162E +# remote_as: '65563' +# router_id: 192.168.1.1 +# vrfs: +# - allocate_index: 5000 +# local_as: '200' +# log_neighbor_changes: true +# neighbors: +# - description: site-1-nbr-1 +# neighbor_address: 198.51.100.1 +# password: +# encryption: 3 +# key: 13D4D3549493D2877B1DC116EE27A6BE +# remote_as: '65562' +# - description: site-1-nbr-2 +# neighbor_address: 198.51.100.2 +# remote_as: '65562' +# vrf: site-1 +# - local_as: '300' +# log_neighbor_changes: true +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - description: site-2-nbr-1 +# neighbor_address: 203.0.113.2 +# password: +# encryption: 3 +# key: AF92F4C16A0A0EC5BDF56CF58BC030F6 +# remote_as: '65568' +# vrf: site-2 + + +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65563 +# router-id 192.168.1.1 +# confederation identifier 42 +# confederation peers 65020 65030 65040 +# bestpath as-path multipath-relax +# bestpath cost-community ignore +# bestpath compare-neighborid +# neighbor-down fib-accelerate +# maxas-limit 20 +# log-neighbor-changes +# neighbor 192.168.1.100 +# low-memory exempt +# bmp-activate-server 1 +# remote-as 65563 +# description NBR-1 +# affinity-group 160 +# neighbor 192.168.1.101 +# remote-as 65563 +# password 7 12090404011C03162E +# vrf site-1 +# local-as 200 +# log-neighbor-changes +# allocate-index 5000 +# neighbor 198.51.100.1 +# remote-as 65562 +# description site-1-nbr-1 +# password 3 13D4D3549493D2877B1DC116EE27A6BE +# neighbor 198.51.100.2 +# remote-as 65562 +# description site-1-nbr-2 +# vrf site-2 +# local-as 300 +# neighbor-down fib-accelerate +# log-neighbor-changes +# neighbor 203.0.113.2 +# remote-as 65568 +# description site-2-nbr-1 +# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6 + +# Using replaced + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65563 +# router-id 192.168.1.1 +# confederation identifier 42 +# confederation peers 65020 65030 65040 +# bestpath as-path multipath-relax +# bestpath cost-community ignore +# bestpath compare-neighborid +# neighbor-down fib-accelerate +# maxas-limit 20 +# log-neighbor-changes +# neighbor 192.168.1.100 +# low-memory exempt +# bmp-activate-server 1 +# remote-as 65563 +# description NBR-1 +# affinity-group 160 +# neighbor 192.168.1.101 +# remote-as 65563 +# password 7 12090404011C03162E +# vrf site-1 +# local-as 200 +# log-neighbor-changes +# allocate-index 5000 +# neighbor 198.51.100.1 +# remote-as 65562 +# description site-1-nbr-1 +# password 3 13D4D3549493D2877B1DC116EE27A6BE +# neighbor 198.51.100.2 +# remote-as 65562 +# description site-1-nbr-2 +# vrf site-2 +# local-as 300 +# neighbor-down fib-accelerate +# log-neighbor-changes +# neighbor 203.0.113.2 +# remote-as 65568 +# description site-2-nbr-1 +# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6 + +- name: Replace BGP configuration with provided configuration + cisco.nxos.nxos_bgp_global: + config: + as_number: 65563 + router_id: 192.168.1.1 + bestpath: + compare_neighborid: True + cost_community_ignore: True + confederation: + identifier: 42 + peers: + - 65020 + - 65030 + - 65050 + maxas_limit: 40 + neighbors: + - neighbor_address: 192.168.1.100 + neighbor_affinity_group: + group_id: 160 + bmp_activate_server: 1 + remote_as: 65563 + description: NBR-1 + low_memory: + exempt: True + neighbor_down: + fib_accelerate: True + vrfs: + - vrf: site-2 + local_as: 300 + log_neighbor_changes: True + neighbors: + - neighbor_address: 203.0.113.2 + password: + encryption: 7 + key: 12090404011C03162E + neighbor_down: + fib_accelerate: True + state: replaced + +# Task output +# ------------- +# before: +# as_number: '65563' +# bestpath: +# as_path: +# multipath_relax: true +# compare_neighborid: true +# cost_community_ignore: true +# confederation: +# identifier: '42' +# peers: +# - '65020' +# - '65030' +# - '65040' +# log_neighbor_changes: true +# maxas_limit: 20 +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - bmp_activate_server: 1 +# description: NBR-1 +# low_memory: +# exempt: true +# neighbor_address: 192.168.1.100 +# neighbor_affinity_group: +# group_id: 160 +# remote_as: '65563' +# - neighbor_address: 192.168.1.101 +# password: +# encryption: 7 +# key: 12090404011C03162E +# remote_as: '65563' +# router_id: 192.168.1.1 +# vrfs: +# - allocate_index: 5000 +# local_as: '200' +# log_neighbor_changes: true +# neighbors: +# - description: site-1-nbr-1 +# neighbor_address: 198.51.100.1 +# password: +# encryption: 3 +# key: 13D4D3549493D2877B1DC116EE27A6BE +# remote_as: '65562' +# - description: site-1-nbr-2 +# neighbor_address: 198.51.100.2 +# remote_as: '65562' +# vrf: site-1 +# - local_as: '300' +# log_neighbor_changes: true +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - description: site-2-nbr-1 +# neighbor_address: 203.0.113.2 +# password: +# encryption: 3 +# key: AF92F4C16A0A0EC5BDF56CF58BC030F6 +# remote_as: '65568' +# vrf: site-2 +# +# commands: +# - router bgp 65563 +# - no bestpath as-path multipath-relax +# - no log-neighbor-changes +# - maxas-limit 40 +# - no confederation peers 65020 65030 65040 +# - confederation peers 65020 65030 65050 +# - no neighbor 192.168.1.101 +# - vrf site-2 +# - neighbor 203.0.113.2 +# - no remote-as 65568 +# - no description site-2-nbr-1 +# - password 7 12090404011C03162E +# - no vrf site-1 + +# after: +# as_number: '65563' +# bestpath: +# compare_neighborid: true +# cost_community_ignore: true +# confederation: +# identifier: '42' +# peers: +# - '65020' +# - '65030' +# - '65050' +# maxas_limit: 40 +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - bmp_activate_server: 1 +# description: NBR-1 +# low_memory: +# exempt: true +# neighbor_address: 192.168.1.100 +# neighbor_affinity_group: +# group_id: 160 +# remote_as: '65563' +# router_id: 192.168.1.1 +# vrfs: +# - local_as: '300' +# log_neighbor_changes: true +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - neighbor_address: 203.0.113.2 +# password: +# encryption: 7 +# key: 12090404011C03162E +# vrf: site-2 +# +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65563 +# router-id 192.168.1.1 +# confederation identifier 42 +# confederation peers 65020 65030 65050 +# bestpath cost-community ignore +# bestpath compare-neighborid +# neighbor-down fib-accelerate +# maxas-limit 40 +# neighbor 192.168.1.100 +# low-memory exempt +# bmp-activate-server 1 +# remote-as 65563 +# description NBR-1 +# affinity-group 160 +# vrf site-2 +# local-as 300 +# neighbor-down fib-accelerate +# log-neighbor-changes +# neighbor 203.0.113.2 +# password 7 12090404011C03162E + +# Using deleted + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65563 +# router-id 192.168.1.1 +# confederation identifier 42 +# confederation peers 65020 65030 65040 +# bestpath as-path multipath-relax +# bestpath cost-community ignore +# bestpath compare-neighborid +# neighbor-down fib-accelerate +# maxas-limit 20 +# log-neighbor-changes +# address-family ipv4 unicast +# default-metric 400 +# suppress-inactive +# default-information originate +# address-family ipv6 multicast +# wait-igp-convergence +# redistribute eigrp eigrp-1 route-map site-1-rmap +# neighbor 192.168.1.100 +# low-memory exempt +# bmp-activate-server 1 +# remote-as 65563 +# description NBR-1 +# affinity-group 160 +# neighbor 192.168.1.101 +# remote-as 65563 +# password 7 12090404011C03162E +# vrf site-1 +# local-as 200 +# log-neighbor-changes +# allocate-index 5000 +# address-family ipv4 multicast +# maximum-paths 40 +# dampen-igp-metric 1200 +# neighbor 198.51.100.1 +# remote-as 65562 +# description site-1-nbr-1 +# password 3 13D4D3549493D2877B1DC116EE27A6BE +# neighbor 198.51.100.2 +# remote-as 65562 +# description site-1-nbr-2 +# vrf site-2 +# local-as 300 +# neighbor-down fib-accelerate +# log-neighbor-changes +# neighbor 203.0.113.2 +# remote-as 65568 +# description site-1-nbr-1 +# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6 + +- name: Delete BGP configurations handled by this module + cisco.nxos.nxos_bgp_global: + state: deleted + +# Task output +# ------------- + +# before: +# as_number: '65563' +# bestpath: +# as_path: +# multipath_relax: true +# compare_neighborid: true +# cost_community_ignore: true +# confederation: +# identifier: '42' +# peers: +# - '65020' +# - '65030' +# - '65040' +# log_neighbor_changes: true +# maxas_limit: 20 +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - bmp_activate_server: 1 +# description: NBR-1 +# low_memory: +# exempt: true +# neighbor_address: 192.168.1.100 +# neighbor_affinity_group: +# group_id: 160 +# remote_as: '65563' +# - neighbor_address: 192.168.1.101 +# password: +# encryption: 7 +# key: 12090404011C03162E +# remote_as: '65563' +# router_id: 192.168.1.1 +# vrfs: +# - allocate_index: 5000 +# local_as: '200' +# log_neighbor_changes: true +# neighbors: +# - description: site-1-nbr-1 +# neighbor_address: 198.51.100.1 +# password: +# encryption: 3 +# key: 13D4D3549493D2877B1DC116EE27A6BE +# remote_as: '65562' +# - description: site-1-nbr-2 +# neighbor_address: 198.51.100.2 +# remote_as: '65562' +# vrf: site-1 +# - local_as: '300' +# log_neighbor_changes: true +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - description: site-1-nbr-1 +# neighbor_address: 203.0.113.2 +# password: +# encryption: 3 +# key: AF92F4C16A0A0EC5BDF56CF58BC030F6 +# remote_as: '65568' +# vrf: site-2 +# +# commands: +# - router bgp 65563 +# - no bestpath as-path multipath-relax +# - no bestpath compare-neighborid +# - no bestpath cost-community ignore +# - no confederation identifier 42 +# - no log-neighbor-changes +# - no maxas-limit 20 +# - no neighbor-down fib-accelerate +# - no router-id 192.168.1.1 +# - no confederation peers 65020 65030 65040 +# - no neighbor 192.168.1.100 +# - no neighbor 192.168.1.101 +# - no vrf site-1 +# - no vrf site-2 +# +# after: +# as_number: '65563' +# +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65563 +# address-family ipv4 unicast +# default-metric 400 +# suppress-inactive +# default-information originate +# address-family ipv6 multicast +# wait-igp-convergence +# redistribute eigrp eigrp-1 route-map site-1-rmap +# + +# Using purged + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65563 +# router-id 192.168.1.1 +# confederation identifier 42 +# confederation peers 65020 65030 65040 +# bestpath as-path multipath-relax +# bestpath cost-community ignore +# bestpath compare-neighborid +# neighbor-down fib-accelerate +# maxas-limit 20 +# log-neighbor-changes +# address-family ipv4 unicast +# default-metric 400 +# suppress-inactive +# default-information originate +# address-family ipv6 multicast +# wait-igp-convergence +# redistribute eigrp eigrp-1 route-map site-1-rmap +# neighbor 192.168.1.100 +# low-memory exempt +# bmp-activate-server 1 +# remote-as 65563 +# description NBR-1 +# affinity-group 160 +# neighbor 192.168.1.101 +# remote-as 65563 +# password 7 12090404011C03162E +# vrf site-1 +# local-as 200 +# log-neighbor-changes +# allocate-index 5000 +# address-family ipv4 multicast +# maximum-paths 40 +# dampen-igp-metric 1200 +# neighbor 198.51.100.1 +# remote-as 65562 +# description site-1-nbr-1 +# password 3 13D4D3549493D2877B1DC116EE27A6BE +# neighbor 198.51.100.2 +# remote-as 65562 +# description site-1-nbr-2 +# vrf site-2 +# local-as 300 +# neighbor-down fib-accelerate +# log-neighbor-changes +# neighbor 203.0.113.2 +# remote-as 65568 +# description site-1-nbr-1 +# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6 + +- name: Purge all BGP configurations from the device + cisco.nxos.nxos_bgp_global: + state: purged + +# Task output +# ------------- + +# before: +# as_number: '65563' +# bestpath: +# as_path: +# multipath_relax: true +# compare_neighborid: true +# cost_community_ignore: true +# confederation: +# identifier: '42' +# peers: +# - '65020' +# - '65030' +# - '65040' +# log_neighbor_changes: true +# maxas_limit: 20 +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - bmp_activate_server: 1 +# description: NBR-1 +# low_memory: +# exempt: true +# neighbor_address: 192.168.1.100 +# neighbor_affinity_group: +# group_id: 160 +# remote_as: '65563' +# - neighbor_address: 192.168.1.101 +# password: +# encryption: 7 +# key: 12090404011C03162E +# remote_as: '65563' +# router_id: 192.168.1.1 +# vrfs: +# - allocate_index: 5000 +# local_as: '200' +# log_neighbor_changes: true +# neighbors: +# - description: site-1-nbr-1 +# neighbor_address: 198.51.100.1 +# password: +# encryption: 3 +# key: 13D4D3549493D2877B1DC116EE27A6BE +# remote_as: '65562' +# - description: site-1-nbr-2 +# neighbor_address: 198.51.100.2 +# remote_as: '65562' +# vrf: site-1 +# - local_as: '300' +# log_neighbor_changes: true +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - description: site-1-nbr-1 +# neighbor_address: 203.0.113.2 +# password: +# encryption: 3 +# key: AF92F4C16A0A0EC5BDF56CF58BC030F6 +# remote_as: '65568' +# vrf: site-2 +# +# commands: +# - no router bgp 65563 +# +# after: {} +# +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# Nexus9000v# + +# Using rendered + +- name: Render platform specific configuration lines (without connecting to the device) + cisco.nxos.nxos_bgp_global: + config: + as_number: 65563 + router_id: 192.168.1.1 + bestpath: + as_path: + multipath_relax: True + compare_neighborid: True + cost_community_ignore: True + confederation: + identifier: 42 + peers: + - 65020 + - 65030 + - 65040 + log_neighbor_changes: True + maxas_limit: 20 + neighbors: + - neighbor_address: 192.168.1.100 + neighbor_affinity_group: + group_id: 160 + bmp_activate_server: 1 + remote_as: 65563 + description: NBR-1 + low_memory: + exempt: True + - neighbor_address: 192.168.1.101 + remote_as: 65563 + password: + encryption: 7 + key: 12090404011C03162E + neighbor_down: + fib_accelerate: True + vrfs: + - vrf: site-1 + allocate_index: 5000 + local_as: 200 + log_neighbor_changes: True + neighbors: + - neighbor_address: 198.51.100.1 + description: site-1-nbr-1 + password: + encryption: 3 + key: 13D4D3549493D2877B1DC116EE27A6BE + remote_as: 65562 + - neighbor_address: 198.51.100.2 + remote_as: 65562 + description: site-1-nbr-2 + - vrf: site-2 + local_as: 300 + log_neighbor_changes: True + neighbors: + - neighbor_address: 203.0.113.2 + description: site-1-nbr-1 + password: + encryption: 3 + key: AF92F4C16A0A0EC5BDF56CF58BC030F6 + remote_as: 65568 + neighbor_down: + fib_accelerate: True + +# Task Output (redacted) +# ----------------------- +# rendered: +# - router bgp 65563 +# - bestpath as-path multipath-relax +# - bestpath compare-neighborid +# - bestpath cost-community ignore +# - confederation identifier 42 +# - log-neighbor-changes +# - maxas-limit 20 +# - neighbor-down fib-accelerate +# - router-id 192.168.1.1 +# - confederation peers 65020 65030 65040 +# - neighbor 192.168.1.100 +# - remote-as 65563 +# - affinity-group 160 +# - bmp-activate-server 1 +# - description NBR-1 +# - low-memory exempt +# - neighbor 192.168.1.101 +# - remote-as 65563 +# - password 7 12090404011C03162E +# - vrf site-1 +# - allocate-index 5000 +# - local-as 200 +# - log-neighbor-changes +# - neighbor 198.51.100.1 +# - remote-as 65562 +# - description site-1-nbr-1 +# - password 3 13D4D3549493D2877B1DC116EE27A6BE +# - neighbor 198.51.100.2 +# - remote-as 65562 +# - description site-1-nbr-2 +# - vrf site-2 +# - local-as 300 +# - log-neighbor-changes +# - neighbor-down fib-accelerate +# - neighbor 203.0.113.2 +# - remote-as 65568 +# - description site-1-nbr-1 +# - password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6 + +# Using parsed + +# parsed.cfg +# ------------ +# router bgp 65563 +# router-id 192.168.1.1 +# confederation identifier 42 +# confederation peers 65020 65030 65040 +# bestpath as-path multipath-relax +# bestpath cost-community ignore +# bestpath compare-neighborid +# neighbor-down fib-accelerate +# maxas-limit 20 +# log-neighbor-changes +# neighbor 192.168.1.100 +# low-memory exempt +# bmp-activate-server 1 +# remote-as 65563 +# description NBR-1 +# affinity-group 160 +# neighbor 192.168.1.101 +# remote-as 65563 +# password 7 12090404011C03162E +# vrf site-1 +# local-as 200 +# log-neighbor-changes +# allocate-index 5000 +# neighbor 198.51.100.1 +# remote-as 65562 +# description site-1-nbr-1 +# password 3 13D4D3549493D2877B1DC116EE27A6BE +# neighbor 198.51.100.2 +# remote-as 65562 +# description site-1-nbr-2 +# vrf site-2 +# local-as 300 +# neighbor-down fib-accelerate +# log-neighbor-changes +# neighbor 203.0.113.2 +# remote-as 65568 +# description site-1-nbr-1 +# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6 + +- name: Parse externally provided BGP config + cisco.nxos.nxos_bgp_global: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# as_number: '65563' +# bestpath: +# as_path: +# multipath_relax: true +# compare_neighborid: true +# cost_community_ignore: true +# confederation: +# identifier: '42' +# peers: +# - '65020' +# - '65030' +# - '65040' +# log_neighbor_changes: true +# maxas_limit: 20 +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - bmp_activate_server: 1 +# description: NBR-1 +# low_memory: +# exempt: true +# neighbor_address: 192.168.1.100 +# neighbor_affinity_group: +# group_id: 160 +# remote_as: '65563' +# - neighbor_address: 192.168.1.101 +# password: +# encryption: 7 +# key: 12090404011C03162E +# remote_as: '65563' +# router_id: 192.168.1.1 +# vrfs: +# - allocate_index: 5000 +# local_as: '200' +# log_neighbor_changes: true +# neighbors: +# - description: site-1-nbr-1 +# neighbor_address: 198.51.100.1 +# password: +# encryption: 3 +# key: 13D4D3549493D2877B1DC116EE27A6BE +# remote_as: '65562' +# - description: site-1-nbr-2 +# neighbor_address: 198.51.100.2 +# remote_as: '65562' +# vrf: site-1 +# - local_as: '300' +# log_neighbor_changes: true +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - description: site-1-nbr-1 +# neighbor_address: 203.0.113.2 +# password: +# encryption: 3 +# key: AF92F4C16A0A0EC5BDF56CF58BC030F6 +# remote_as: '65568' +# vrf: site-2 + +# Using gathered + +# existing config +# +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65563 +# router-id 192.168.1.1 +# confederation identifier 42 +# confederation peers 65020 65030 65050 +# bestpath cost-community ignore +# bestpath compare-neighborid +# neighbor-down fib-accelerate +# maxas-limit 40 +# neighbor 192.168.1.100 +# low-memory exempt +# bmp-activate-server 1 +# remote-as 65563 +# description NBR-1 +# affinity-group 160 +# vrf site-1 +# vrf site-2 +# local-as 300 +# neighbor-down fib-accelerate +# log-neighbor-changes +# neighbor 203.0.113.2 +# password 7 12090404011C03162E + +- name: Gather BGP facts using gathered + cisco.nxos.nxos_bgp_global: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# as_number: '65563' +# bestpath: +# compare_neighborid: true +# cost_community_ignore: true +# confederation: +# identifier: '42' +# peers: +# - '65020' +# - '65030' +# - '65050' +# maxas_limit: 40 +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - bmp_activate_server: 1 +# description: NBR-1 +# low_memory: +# exempt: true +# neighbor_address: 192.168.1.100 +# neighbor_affinity_group: +# group_id: 160 +# remote_as: '65563' +# router_id: 192.168.1.1 +# vrfs: +# - vrf: site-1 +# - local_as: '300' +# log_neighbor_changes: true +# neighbor_down: +# fib_accelerate: true +# neighbors: +# - neighbor_address: 203.0.113.2 +# password: +# encryption: 7 +# key: 12090404011C03162E +# vrf: site-2 + +# Remove a neighbor having AF configurations with state replaced (will fail) + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# log-neighbor-changes +# maxas-limit 20 +# router-id 198.51.100.2 +# neighbor 203.0.113.2 +# address-family ipv4 unicast +# next-hop-self +# remote-as 65538 +# affinity-group 160 +# description NBR-1 +# low-memory exempt +# neighbor 192.0.2.1 +# remote-as 65537 +# password 7 12090404011C03162E + +- name: Remove a neighbor having AF configurations (should fail) + cisco.nxos.nxos_bgp_global: + config: + as_number: 65536 + router_id: 198.51.100.2 + maxas_limit: 20 + log_neighbor_changes: True + neighbors: + - neighbor_address: 192.0.2.1 + remote_as: 65537 + password: + encryption: 7 + key: 12090404011C03162E + state: replaced + +# Task output (redacted) +# ----------------------- +# fatal: [Nexus9000v]: FAILED! => changed=false +# msg: Neighbor 203.0.113.2 has address-family configurations. +# Please use the nxos_bgp_neighbor_af module to remove those first. + +# Remove a VRF having AF configurations with state replaced (will fail) + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# log-neighbor-changes +# maxas-limit 20 +# router-id 198.51.100.2 +# neighbor 192.0.2.1 +# remote-as 65537 +# password 7 12090404011C03162E +# vrf site-1 +# address-family ipv4 unicast +# default-information originate +# neighbor 203.0.113.2 +# remote-as 65538 +# affinity-group 160 +# description NBR-1 +# low-memory exempt +# vrf site-2 +# neighbor-down fib-accelerate + +- name: Remove a VRF having AF configurations (should fail) + cisco.nxos.nxos_bgp_global: + config: + as_number: 65536 + router_id: 198.51.100.2 + maxas_limit: 20 + log_neighbor_changes: True + neighbors: + - neighbor_address: 192.0.2.1 + remote_as: 65537 + password: + encryption: 7 + key: 12090404011C03162E + vrfs: + - vrf: site-2 + neighbor_down: + fib_accelerate: True + state: replaced + +# Task output (redacted) +# ----------------------- +# fatal: [Nexus9000v]: FAILED! => changed=false +# msg: VRF site-1 has address-family configurations. +# Please use the nxos_bgp_af module to remove those first. +""" + +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: + - router bgp 65563 + - maxas-limit 20 + - router-id 192.168.1.1 + - confederation peers 65020 65030 65040 + - neighbor 192.168.1.100 + - remote-as 65563 + - affinity-group 160 + - bmp-activate-server 1 + - description NBR-1 + - low-memory exempt + - vrf site-1 + - log-neighbor-changes + - neighbor 198.51.100.1 + - remote-as 65562 + - description site-1-nbr-1 + - password 3 13D4D3549493D2877B1DC116EE27A6BE +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_global.bgp_global import ( + Bgp_globalArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_global.bgp_global import ( + Bgp_global, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Bgp_globalArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Bgp_global(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py new file mode 100644 index 00000000..2c5283ca --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py @@ -0,0 +1,569 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_bgp_neighbor +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2023-01-27) Manages BGP neighbors configurations. +description: +- Manages BGP neighbors configurations on NX-OS switches. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +deprecated: + alternative: nxos_bgp_global + why: Updated module released with more functionality. + removed_at_date: '2023-01-27' +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- C(state=absent) removes the whole BGP neighbor configuration. +- Default, where supported, restores params default value. +options: + asn: + description: + - BGP autonomous system number. Valid values are string, Integer in ASPLAIN or + ASDOT notation. + required: true + type: str + vrf: + description: + - Name of the VRF. The name 'default' is a valid VRF representing the global bgp. + default: default + type: str + neighbor: + description: + - Neighbor Identifier. Valid values are string. Neighbors may use IPv4 or IPv6 + notation, with or without prefix length. + required: true + type: str + description: + description: + - Description of the neighbor. + type: str + bfd: + description: + - Enables/Disables BFD for a given neighbor. + - "Dependency: ''feature bfd''" + type: str + choices: + - enable + - disable + connected_check: + description: + - Configure whether or not to check for directly connected peer. + type: bool + capability_negotiation: + description: + - Configure whether or not to negotiate capability with this neighbor. + type: bool + dynamic_capability: + description: + - Configure whether or not to enable dynamic capability. + type: bool + ebgp_multihop: + description: + - Specify multihop TTL for a remote peer. Valid values are integers between 2 + and 255, or keyword 'default' to disable this property. + type: str + local_as: + description: + - Specify the local-as number for the eBGP neighbor. Valid values are String or + Integer in ASPLAIN or ASDOT notation, or 'default', which means not to configure + it. + type: str + log_neighbor_changes: + description: + - Specify whether or not to enable log messages for neighbor up/down event. + choices: + - enable + - disable + - inherit + type: str + low_memory_exempt: + description: + - Specify whether or not to shut down this neighbor under memory pressure. + type: bool + maximum_peers: + description: + - Specify Maximum number of peers for this neighbor prefix Valid values are between + 1 and 1000, or 'default', which does not impose the limit. Note that this parameter + is accepted only on neighbors with address/prefix. + type: str + pwd: + description: + - Specify the password for neighbor. Valid value is string. + type: str + pwd_type: + description: + - Specify the encryption type the password will use. Valid values are '3des' or + 'cisco_type_7' encryption or keyword 'default'. + choices: + - 3des + - cisco_type_7 + - default + type: str + remote_as: + description: + - Specify Autonomous System Number of the neighbor. Valid values are String or + Integer in ASPLAIN or ASDOT notation, or 'default', which means not to configure + it. + type: str + remove_private_as: + description: + - Specify the config to remove private AS number from outbound updates. Valid + values are 'enable' to enable this config, 'disable' to disable this config, + 'all' to remove all private AS number, or 'replace-as', to replace the private + AS number. + choices: + - enable + - disable + - all + - replace-as + type: str + shutdown: + description: + - Configure to administratively shutdown this neighbor. + type: bool + suppress_4_byte_as: + description: + - Configure to suppress 4-byte AS Capability. + type: bool + timers_keepalive: + description: + - Specify keepalive timer value. Valid values are integers between 0 and 3600 + in terms of seconds, or 'default', which is 60. + type: str + timers_holdtime: + description: + - Specify holdtime timer value. Valid values are integers between 0 and 3600 in + terms of seconds, or 'default', which is 180. + type: str + transport_passive_only: + description: + - Specify whether or not to only allow passive connection setup. Valid values + are 'true', 'false', and 'default', which defaults to 'false'. This property + can only be configured when the neighbor is in 'ip' address format without prefix + length. + type: bool + update_source: + description: + - Specify source interface of BGP session and updates. + type: str + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: + - present + - absent + type: str + peer_type: + description: + - Specify the peer type for BGP session. + choices: + - fabric_border_leaf + - fabric_external + - disable + type: str + version_added: 1.1.0 +""" +EXAMPLES = """ +# create a new neighbor +- cisco.nxos.nxos_bgp_neighbor: + asn: 65535 + neighbor: 192.0.2.3 + local_as: 20 + remote_as: 30 + bfd: enable + description: just a description + update_source: Ethernet1/3 + state: present + peer_type: fabric_external +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["router bgp 65535", "neighbor 192.0.2.3", + "remote-as 30", "update-source Ethernet1/3", + "description just a description", "local-as 20", "peer-type fabric-external"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +BOOL_PARAMS = [ + "capability_negotiation", + "shutdown", + "connected_check", + "dynamic_capability", + "low_memory_exempt", + "suppress_4_byte_as", + "transport_passive_only", +] +PARAM_TO_COMMAND_KEYMAP = { + "asn": "router bgp", + "bfd": "bfd", + "capability_negotiation": "dont-capability-negotiate", + "connected_check": "disable-connected-check", + "description": "description", + "dynamic_capability": "dynamic-capability", + "ebgp_multihop": "ebgp-multihop", + "local_as": "local-as", + "log_neighbor_changes": "log-neighbor-changes", + "low_memory_exempt": "low-memory exempt", + "maximum_peers": "maximum-peers", + "neighbor": "neighbor", + "pwd": "password", + "pwd_type": "password", + "remote_as": "remote-as", + "remove_private_as": "remove-private-as", + "shutdown": "shutdown", + "suppress_4_byte_as": "capability suppress 4-byte-as", + "timers_keepalive": "timers", + "timers_holdtime": "timers", + "transport_passive_only": "transport connection-mode passive", + "update_source": "update-source", + "vrf": "vrf", + "peer_type": "peer-type", +} +PARAM_TO_DEFAULT_KEYMAP = { + "bfd": "disable", + "shutdown": False, + "dynamic_capability": True, + "timers_keepalive": 60, + "timers_holdtime": 180, + "peer_type": "disable", +} + + +def get_value(arg, config): + command = PARAM_TO_COMMAND_KEYMAP[arg] + has_command = re.search(r"^\s+{0}$".format(command), config, re.M) + has_command_val = re.search(r"(?:\s+{0}\s*)(?P<value>.*)$".format(command), config, re.M) + + if arg == "dynamic_capability": + has_no_command = re.search(r"\s+no\s{0}\s*$".format(command), config, re.M) + value = True + if has_no_command: + value = False + elif arg in BOOL_PARAMS: + value = False + if has_command: + value = True + elif arg == "log_neighbor_changes": + value = "" + if has_command: + value = "enable" + elif has_command_val: + value = "disable" + + elif arg == "remove_private_as": + value = "disable" + if has_command: + value = "enable" + elif has_command_val: + value = has_command_val.group("value") + elif arg == "bfd": + value = "enable" if has_command else "disable" + + elif arg == "peer_type": + value = "disable" + if has_command_val: + value = has_command_val.group("value").replace("-", "_") + else: + value = "" + + if has_command_val: + value = has_command_val.group("value") + + if command in ["timers", "password"]: + split_value = value.split() + value = "" + + if arg in ["timers_keepalive", "pwd_type"]: + value = split_value[0] + elif arg in ["timers_holdtime", "pwd"] and len(split_value) == 2: + value = split_value[1] + + return value + + +def get_existing(module, args, warnings): + existing = {} + netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) + + asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S) + match_asn = asn_regex.match(str(netcfg)) + + if match_asn: + existing_asn = match_asn.group("existing_asn") + parents = ["router bgp {0}".format(existing_asn)] + + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + parents.append("neighbor {0}".format(module.params["neighbor"])) + config = netcfg.get_section(parents) + if config: + for arg in args: + if arg not in ["asn", "vrf", "neighbor"]: + existing[arg] = get_value(arg, config) + + existing["asn"] = existing_asn + existing["neighbor"] = module.params["neighbor"] + existing["vrf"] = module.params["vrf"] + else: + warnings.append("The BGP process didn't exist but the task just created it.") + return existing + + +def apply_key_map(key_map, table): + new_dict = {} + for key in table: + new_key = key_map.get(key) + if new_key: + new_dict[new_key] = table.get(key) + + return new_dict + + +def state_present(module, existing, proposed, candidate): + commands = list() + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) + existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) + + for key, value in proposed_commands.items(): + if value is True: + commands.append(key) + elif value is False: + commands.append("no {0}".format(key)) + elif value == "default": + if existing_commands.get(key): + if key == "password": + commands.append("no password") + else: + existing_value = existing_commands.get(key) + commands.append("no {0} {1}".format(key, existing_value)) + else: + if key == "log-neighbor-changes": + if value == "enable": + commands.append("{0}".format(key)) + elif value == "disable": + commands.append("{0} {1}".format(key, value)) + elif value == "inherit": + if existing_commands.get(key): + commands.append("no {0}".format(key)) + elif key == "password": + pwd_type = module.params["pwd_type"] + if pwd_type == "3des": + pwd_type = 3 + else: + pwd_type = 7 + command = "{0} {1} {2}".format(key, pwd_type, value) + if command not in commands: + commands.append(command) + elif key == "remove-private-as": + if value == "enable": + command = "{0}".format(key) + commands.append(command) + elif value == "disable": + if existing_commands.get(key) != "disable": + command = "no {0}".format(key) + commands.append(command) + else: + command = "{0} {1}".format(key, value) + commands.append(command) + elif key == "timers": + if proposed["timers_keepalive"] != PARAM_TO_DEFAULT_KEYMAP.get( + "timers_keepalive", + ) or proposed["timers_holdtime"] != PARAM_TO_DEFAULT_KEYMAP.get("timers_holdtime"): + command = "timers {0} {1}".format( + proposed["timers_keepalive"], + proposed["timers_holdtime"], + ) + if command not in commands: + commands.append(command) + elif key == "bfd": + no_cmd = "no " if value == "disable" else "" + commands.append(no_cmd + key) + elif key == "peer-type": + if value == "disable": + if existing_commands.get(key) != "disable": + command = "no {0}".format(key) + commands.append(command) + elif value == "fabric_external": + ptype = "fabric-external" + command = "{0} {1}".format(key, ptype) + commands.append(command) + elif value == "fabric_border_leaf": + ptype = "fabric-border-leaf" + command = "{0} {1}".format(key, ptype) + commands.append(command) + else: + command = "{0} {1}".format(key, value) + commands.append(command) + + if commands: + parents = ["router bgp {0}".format(module.params["asn"])] + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + parents.append("neighbor {0}".format(module.params["neighbor"])) + + # make sure that local-as is the last command in the list. + local_as_command = "local-as {0}".format(module.params["local_as"]) + if local_as_command in commands: + commands.remove(local_as_command) + commands.append(local_as_command) + candidate.add(commands, parents=parents) + + +def state_absent(module, existing, proposed, candidate): + commands = [] + parents = ["router bgp {0}".format(module.params["asn"])] + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + commands.append("no neighbor {0}".format(module.params["neighbor"])) + candidate.add(commands, parents=parents) + + +def main(): + argument_spec = dict( + asn=dict(required=True, type="str"), + vrf=dict(required=False, type="str", default="default"), + neighbor=dict(required=True, type="str"), + description=dict(required=False, type="str"), + bfd=dict(required=False, type="str", choices=["enable", "disable"]), + capability_negotiation=dict(required=False, type="bool"), + connected_check=dict(required=False, type="bool"), + dynamic_capability=dict(required=False, type="bool"), + ebgp_multihop=dict(required=False, type="str"), + local_as=dict(required=False, type="str"), + log_neighbor_changes=dict( + required=False, + type="str", + choices=["enable", "disable", "inherit"], + ), + low_memory_exempt=dict(required=False, type="bool"), + maximum_peers=dict(required=False, type="str"), + pwd=dict(required=False, type="str"), + pwd_type=dict( + required=False, + type="str", + choices=["3des", "cisco_type_7", "default"], + ), + remote_as=dict(required=False, type="str"), + remove_private_as=dict( + required=False, + type="str", + choices=["enable", "disable", "all", "replace-as"], + ), + shutdown=dict(required=False, type="bool"), + suppress_4_byte_as=dict(required=False, type="bool"), + timers_keepalive=dict(required=False, type="str"), + timers_holdtime=dict(required=False, type="str"), + transport_passive_only=dict(required=False, type="bool"), + update_source=dict(required=False, type="str"), + state=dict(choices=["present", "absent"], default="present", required=False), + peer_type=dict( + required=False, + type="str", + choices=["disable", "fabric_border_leaf", "fabric_external"], + ), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=[ + ["timers_holdtime", "timers_keepalive"], + ["pwd", "pwd_type"], + ], + supports_check_mode=True, + ) + + warnings = list() + result = dict(changed=False, warnings=warnings) + + state = module.params["state"] + + if module.params["pwd_type"] == "default": + module.params["pwd_type"] = "0" + + args = PARAM_TO_COMMAND_KEYMAP.keys() + existing = get_existing(module, args, warnings) + + if existing.get("asn") and state == "present": + if existing["asn"] != module.params["asn"]: + module.fail_json( + msg="Another BGP ASN already exists.", + proposed_asn=module.params["asn"], + existing_asn=existing.get("asn"), + ) + + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + proposed = {} + for key, value in proposed_args.items(): + if key not in ["asn", "vrf", "neighbor", "pwd_type"]: + if str(value).lower() == "default": + value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default") + if key == "bfd": + if existing.get("bfd", "disable") != value: + proposed[key] = value + elif existing.get(key) != value: + proposed[key] = value + + candidate = CustomNetworkConfig(indent=3) + if state == "present": + state_present(module, existing, proposed, candidate) + elif state == "absent" and existing: + state_absent(module, existing, proposed, candidate) + + if candidate: + candidate = candidate.items_text() + if not module.check_mode: + load_config(module, candidate) + result["changed"] = True + result["commands"] = candidate + else: + result["commands"] = [] + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py new file mode 100644 index 00000000..967f8ee4 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py @@ -0,0 +1,1155 @@ +#!/usr/bin/python +# -*- 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 module file for nxos_bgp_neighbor_address_family +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_bgp_neighbor_address_family +short_description: BGP Neighbor Address Family resource module. +description: +- This module manages BGP Neighbor Address Family configuration on devices running Cisco NX-OS. +version_added: 2.0.0 +notes: +- Tested against NX-OS 9.3.6. +- Unsupported for Cisco MDS +- For managing BGP address family configurations please use + the M(cisco.nxos.nxos_bgp_address_family) module. +- This module works with connection C(network_cli) and C(httpapi). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^router bgp'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: BGP Neighbor AF configuration. + type: dict + suboptions: + as_number: + description: Autonomous System Number of the router. + type: str + neighbors: &nbr + description: A list of BGP Neighbor AF configuration. + type: list + elements: dict + suboptions: + neighbor_address: + description: IP/IPv6 address of the neighbor. + type: str + required: True + address_family: + description: BGP Neighbor Address Family related configurations. + type: list + elements: dict + suboptions: + afi: + description: Address Family indicator. + type: str + choices: ["ipv4", "ipv6", "link-state", "vpnv4", "vpnv6", "l2vpn"] + required: True + safi: + description: Sub Address Family indicator. + type: str + choices: ["unicast", "multicast", "mvpn", "evpn"] + advertise_map: + description: Specify route-map for conditional advertisement. + type: dict + suboptions: + route_map: + description: Route-map name. + type: str + required: True + exist_map: + description: Condition route-map to advertise only when prefix in condition exists. + type: str + non_exist_map: + description: Condition route-map to advertise only when prefix in condition does not exist. + type: str + advertisement_interval: + description: Minimum interval between sending BGP routing updates. + type: int + allowas_in: + description: Accept as-path with my AS present in it. + type: dict + suboptions: + set: + description: Activate allowas-in property. + type: bool + max_occurences: + description: Number of occurrences of AS number, default is 3. + type: int + as_override: + description: Override matching AS-number while sending update. + type: bool + capability: + description: Advertise capability to the peer. + type: dict + suboptions: + additional_paths: + description: Additional paths capability. + type: dict + suboptions: + receive: + description: Additional paths Receive capability. + type: str + choices: ["enable", "disable"] + send: + description: Additional paths Send capability. + type: str + choices: ["enable", "disable"] + default_originate: + description: Originate a default toward this peer. + type: dict + suboptions: + set: + description: Set default-originate attribute. + type: bool + route_map: + description: Route-map to specify criteria for originating default. + type: str + disable_peer_as_check: + description: Disable checking of peer AS-number while advertising. + type: bool + filter_list: + description: Name of filter-list. + type: dict + suboptions: + inbound: + description: Apply policy to incoming routes. + type: str + outbound: + description: Apply policy to outgoing routes. + type: str + inherit: + description: Inherit a template. + type: dict + suboptions: + template: + description: Template name. + type: str + sequence: + description: Sequence number. + type: int + maximum_prefix: + description: Maximum number of prefixes from this neighbor. + type: dict + suboptions: + max_prefix_limit: + description: Maximum prefix limit. + type: int + generate_warning_threshold: + description: Threshold percentage at which to generate a warning. + type: int + restart_interval: + description: Restart bgp connection after limit is exceeded. + type: int + warning_only: + description: Only give a warning message when limit is exceeded. + type: bool + next_hop_self: + description: Set our address as nexthop (non-reflected). + type: dict + suboptions: + set: + description: Set next-hop-self attribute. + type: bool + all_routes: + description: Set our address as nexthop for all routes. + type: bool + next_hop_third_party: + description: Compute a third-party nexthop if possible. + type: bool + prefix_list: + description: Apply prefix-list. + type: dict + suboptions: + inbound: + description: Apply policy to incoming routes. + type: str + outbound: + description: Apply policy to outgoing routes. + type: str + rewrite_evpn_rt_asn: + description: Auto generate RTs for EBGP neighbor. + type: bool + route_map: + description: Apply route-map to neighbor. + type: dict + suboptions: + inbound: + description: Apply policy to incoming routes. + type: str + outbound: + description: Apply policy to outgoing routes. + type: str + route_reflector_client: + description: Configure a neighbor as Route reflector client. + type: bool + send_community: + description: Send Community attribute to this neighbor. + type: dict + suboptions: + set: + description: Set send-community attribute. + type: bool + extended: + description: Send Extended Community attribute. + type: bool + standard: + description: Send Standard Community attribute. + type: bool + both: + description: Send Standard and Extended Community attributes. + type: bool + soft_reconfiguration_inbound: + description: Soft reconfiguration. + type: dict + suboptions: + set: + description: Set soft-reconfiguration inbound attribute. + type: bool + always: + description: Always perform inbound soft reconfiguration. + type: bool + soo: + description: Specify Site-of-origin extcommunity. + type: str + suppress_inactive: + description: Advertise only active routes to peer. + type: bool + unsuppress_map: + description: Route-map to selectively unsuppress suppressed routes. + type: str + weight: + description: Set default weight for routes from this neighbor. + type: int + vrfs: + description: Virtual Router Context. + type: list + elements: dict + suboptions: + vrf: + description: VRF name. + type: str + neighbors: *nbr + state: + description: + - The state the configuration should be left in. + - State I(deleted) only removes BGP attributes that this modules + manages and does not negate the BGP process completely. + - Refer to examples for more details. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# Nexus9000v# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_bgp_neighbor_address_family: &id001 + config: + as_number: 65536 + neighbors: + - neighbor_address: 192.0.2.32 + address_family: + - afi: ipv4 + safi: unicast + maximum_prefix: + max_prefix_limit: 20 + generate_warning_threshold: 75 + weight: 100 + prefix_list: + inbound: rmap1 + outbound: rmap2 + - afi: ipv6 + safi: unicast + - neighbor_address: 192.0.2.33 + address_family: + - afi: ipv4 + safi: multicast + inherit: + template: BasePolicy + sequence: 200 + vrfs: + - vrf: site-1 + neighbors: + - neighbor_address: 203.0.113.1 + address_family: + - afi: ipv4 + safi: unicast + suppress_inactive: True + next_hop_self: + set: True + - neighbor_address: 203.0.113.2 + address_family: + - afi: ipv6 + safi: unicast + - afi: ipv4 + safi: multicast + send_community: + set: True + +# Task output +# ------------- +# before: {} +# +# commands: +# - router bgp 65536 +# - neighbor 192.0.2.32 +# - address-family ipv4 unicast +# - maximum-prefix 20 75 +# - weight 100 +# - prefix-list rmap1 in +# - prefix-list rmap2 out +# - address-family ipv6 unicast +# - neighbor 192.0.2.33 +# - address-family ipv4 multicast +# - inherit peer-policy BasePolicy 200 +# - vrf site-1 +# - neighbor 203.0.113.1 +# - address-family ipv4 unicast +# - suppress-inactive +# - next-hop-self +# - neighbor 203.0.113.2 +# - address-family ipv6 unicast +# - address-family ipv4 multicast +# - send-community +# +# after: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv4 +# safi: unicast +# maximum_prefix: +# max_prefix_limit: 20 +# generate_warning_threshold: 75 +# weight: 100 +# prefix_list: +# inbound: rmap1 +# outbound: rmap2 +# - afi: ipv6 +# safi: unicast +# - neighbor_address: 192.0.2.33 +# address_family: +# - afi: ipv4 +# safi: multicast +# inherit: +# template: BasePolicy +# sequence: 200 +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# suppress_inactive: true +# next_hop_self: +# set: true +# - neighbor_address: 203.0.113.2 +# address_family: +# - afi: ipv4 +# safi: multicast +# send_community: +# set: True +# - afi: ipv6 +# safi: unicast + +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv4 unicast +# maximum-prefix 20 75 +# weight 100 +# prefix-list rmap1 in +# prefix-list rmap2 out +# address-family ipv6 unicast +# neighbor 192.0.2.33 +# address-family ipv4 multicast +# inherit peer-policy BasePolicy 200 +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# suppress-inactive +# next-hop-self +# neighbor 203.0.113.2 +# address-family ipv4 multicast +# send-community +# address-family ipv6 unicast +# + +# Using replaced + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv4 unicast +# maximum-prefix 20 75 +# weight 100 +# prefix-list rmap1 in +# prefix-list rmap2 out +# address-family ipv6 unicast +# neighbor 192.0.2.33 +# address-family ipv4 multicast +# inherit peer-policy BasePolicy 200 +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# suppress-inactive +# next-hop-self +# neighbor 203.0.113.2 +# address-family ipv4 multicast +# send-community +# address-family ipv6 unicast +# + +- name: Replace specified neighbor AFs with given configuration + cisco.nxos.nxos_bgp_neighbor_address_family: &replaced + config: + as_number: 65536 + neighbors: + - neighbor_address: 192.0.2.32 + address_family: + - afi: ipv4 + safi: unicast + weight: 110 + - afi: ipv6 + safi: unicast + - neighbor_address: 192.0.2.33 + address_family: + - afi: ipv4 + safi: multicast + inherit: + template: BasePolicy + sequence: 200 + vrfs: + - vrf: site-1 + neighbors: + - neighbor_address: 203.0.113.1 + address_family: + - afi: ipv4 + safi: unicast + - neighbor_address: 203.0.113.2 + address_family: + - afi: ipv6 + safi: unicast + - afi: ipv4 + safi: multicast + send_community: + set: True + state: replaced + +# Task output +# ------------- +# before: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv4 +# safi: unicast +# maximum_prefix: +# max_prefix_limit: 20 +# generate_warning_threshold: 75 +# weight: 100 +# prefix_list: +# inbound: rmap1 +# outbound: rmap2 +# - afi: ipv6 +# safi: unicast +# - neighbor_address: 192.0.2.33 +# address_family: +# - afi: ipv4 +# safi: multicast +# inherit: +# template: BasePolicy +# sequence: 200 +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# suppress_inactive: true +# next_hop_self: +# set: true +# - neighbor_address: 203.0.113.2 +# address_family: +# - afi: ipv4 +# safi: multicast +# send_community: +# set: True +# - afi: ipv6 +# safi: unicast +# +# commands: +# - router bgp 65536 +# - neighbor 192.0.2.32 +# - address-family ipv4 unicast +# - no maximum-prefix 20 75 +# - weight 110 +# - no prefix-list rmap1 in +# - no prefix-list rmap2 out +# - vrf site-1 +# - neighbor 203.0.113.1 +# - address-family ipv4 unicast +# - no suppress-inactive +# - no next-hop-self +# +# after: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv4 +# safi: unicast +# weight: 110 +# - afi: ipv6 +# safi: unicast +# - neighbor_address: 192.0.2.33 +# address_family: +# - afi: ipv4 +# safi: multicast +# inherit: +# template: BasePolicy +# sequence: 200 +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# - neighbor_address: 203.0.113.2 +# address_family: +# - afi: ipv4 +# safi: multicast +# send_community: +# set: True +# - afi: ipv6 +# safi: unicast + +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv4 unicast +# weight 110 +# address-family ipv6 unicast +# neighbor 192.0.2.33 +# address-family ipv4 multicast +# inherit peer-policy BasePolicy 200 +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# neighbor 203.0.113.2 +# address-family ipv4 multicast +# send-community +# address-family ipv6 unicast +# + +# Using overridden + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv4 unicast +# maximum-prefix 20 75 +# weight 100 +# prefix-list rmap1 in +# prefix-list rmap2 out +# address-family ipv6 unicast +# neighbor 192.0.2.33 +# address-family ipv4 multicast +# inherit peer-policy BasePolicy 200 +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# suppress-inactive +# next-hop-self +# neighbor 203.0.113.2 +# address-family ipv4 multicast +# send-community +# address-family ipv6 unicast +# + +- name: Override all BGP AF configuration with provided configuration + cisco.nxos.nxos_bgp_neighbor_address_family: + config: + as_number: 65536 + neighbors: + - neighbor_address: 192.0.2.32 + address_family: + - afi: ipv4 + safi: unicast + vrfs: + - vrf: site-1 + neighbors: + - neighbor_address: 203.0.113.1 + address_family: + - afi: ipv4 + safi: unicast + suppress_inactive: True + next_hop_self: + set: True + state: overridden + +# Task output +# ------------- +# before: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv4 +# safi: unicast +# maximum_prefix: +# max_prefix_limit: 20 +# generate_warning_threshold: 75 +# weight: 100 +# prefix_list: +# inbound: rmap1 +# outbound: rmap2 +# - afi: ipv6 +# safi: unicast +# - neighbor_address: 192.0.2.33 +# address_family: +# - afi: ipv4 +# safi: multicast +# inherit: +# template: BasePolicy +# sequence: 200 +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# suppress_inactive: true +# next_hop_self: +# set: true +# - neighbor_address: 203.0.113.2 +# address_family: +# - afi: ipv4 +# safi: multicast +# send_community: +# set: True +# - afi: ipv6 +# safi: unicast +# +# commands: +# - router bgp 65536 +# - neighbor 192.0.2.32 +# - address-family ipv4 unicast +# - no maximum-prefix 20 75 +# - no weight 100 +# - no prefix-list rmap1 in +# - no prefix-list rmap2 out +# - no address-family ipv6 unicast +# - neighbor 192.0.2.33 +# - no address-family ipv4 multicast +# - vrf site-1 +# - neighbor 203.0.113.2 +# - no address-family ipv4 multicast +# - no address-family ipv6 unicast +# +# after: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv4 +# safi: unicast +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# suppress_inactive: True +# next_hop_self: +# set: True + +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv4 unicast +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# suppress-inactive +# next-hop-self + +# Using deleted to remove specified neighbor AFs + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv4 unicast +# maximum-prefix 20 75 +# weight 100 +# prefix-list rmap1 in +# prefix-list rmap2 out +# address-family ipv6 unicast +# neighbor 192.0.2.33 +# address-family ipv4 multicast +# inherit peer-policy BasePolicy 200 +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# suppress-inactive +# next-hop-self +# neighbor 203.0.113.2 +# address-family ipv4 multicast +# send-community +# address-family ipv6 unicast +# + +- name: Delete BGP configs handled by this module + cisco.nxos.nxos_bgp_neighbor_address_family: + config: + as_number: 65536 + neighbors: + - neighbor_address: 192.0.2.32 + address_family: + - afi: ipv4 + safi: unicast + vrfs: + - vrf: site-1 + neighbors: + - neighbor_address: 203.0.113.2 + address_family: + - afi: ipv6 + safi: unicast + state: deleted + +# Task output +# ------------- +# before: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv4 +# safi: unicast +# maximum_prefix: +# max_prefix_limit: 20 +# generate_warning_threshold: 75 +# weight: 100 +# prefix_list: +# inbound: rmap1 +# outbound: rmap2 +# - afi: ipv6 +# safi: unicast +# - neighbor_address: 192.0.2.33 +# address_family: +# - afi: ipv4 +# safi: multicast +# inherit: +# template: BasePolicy +# sequence: 200 +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# suppress_inactive: true +# next_hop_self: +# set: true +# - neighbor_address: 203.0.113.2 +# address_family: +# - afi: ipv4 +# safi: multicast +# send_community: +# set: True +# - afi: ipv6 +# safi: unicast +# +# commands: +# - router bgp 65536 +# - neighbor 192.0.2.32 +# - no address-family ipv4 unicast +# - vrf site-1 +# - neighbor 203.0.113.2 +# - no address-family ipv6 unicast +# +# after: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv6 +# safi: unicast +# - neighbor_address: 192.0.2.33 +# address_family: +# - afi: ipv4 +# safi: multicast +# inherit: +# template: BasePolicy +# sequence: 200 +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# suppress_inactive: true +# next_hop_self: +# set: true +# - neighbor_address: 203.0.113.2 +# address_family: +# - afi: ipv4 +# safi: multicast +# send_community: +# set: True +# +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv6 unicast +# neighbor 192.0.2.33 +# address-family ipv4 multicast +# inherit peer-policy BasePolicy 200 +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# suppress-inactive +# next-hop-self +# neighbor 203.0.113.2 +# address-family ipv4 multicast +# send-community +# + +# Using deleted to remove all neighbor AFs + +# Before state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv4 unicast +# maximum-prefix 20 75 +# weight 100 +# prefix-list rmap1 in +# prefix-list rmap2 out +# address-family ipv6 unicast +# neighbor 192.0.2.33 +# address-family ipv4 multicast +# inherit peer-policy BasePolicy 200 +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# suppress-inactive +# next-hop-self +# neighbor 203.0.113.2 +# address-family ipv4 multicast +# send-community +# address-family ipv6 unicast +# + +- name: Delete all BGP neighbor AF configs handled by this module + cisco.nxos.nxos_bgp_neighbor_address_family: + state: deleted + +# Task output +# ------------- +# before: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv4 +# safi: unicast +# maximum_prefix: +# max_prefix_limit: 20 +# generate_warning_threshold: 75 +# weight: 100 +# prefix_list: +# inbound: rmap1 +# outbound: rmap2 +# - afi: ipv6 +# safi: unicast +# - neighbor_address: 192.0.2.33 +# address_family: +# - afi: ipv4 +# safi: multicast +# inherit: +# template: BasePolicy +# sequence: 200 +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# suppress_inactive: true +# next_hop_self: +# set: true +# - neighbor_address: 203.0.113.2 +# address_family: +# - afi: ipv4 +# safi: multicast +# send_community: +# set: True +# - afi: ipv6 +# safi: unicast +# +# commands: +# - router bgp 65536 +# - neighbor 192.0.2.32 +# - no address-family ipv4 unicast +# - no address-family ipv6 unicast +# - neighbor 192.0.2.33 +# - no address-family ipv4 multicast +# - vrf site-1 +# - neighbor 203.0.113.1 +# - no address-family ipv4 unicast +# - neighbor 203.0.113.2 +# - no address-family ipv6 unicast +# - no address-family ipv4 multicast +# +# after: +# as_number: "65536" +# +# After state: +# ------------- +# Nexus9000v# show running-config | section "^router bgp" +# router bgp 65536 +# neighbor 192.0.2.32 +# neighbor 192.0.2.33 +# vrf site-1 +# neighbor 203.0.113.1 +# neighbor 203.0.113.2 +# + +# Using rendered + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_bgp_neighbor_address_family: + config: + as_number: 65536 + neighbors: + - neighbor_address: 192.0.2.32 + address_family: + - afi: ipv4 + safi: unicast + maximum_prefix: + max_prefix_limit: 20 + generate_warning_threshold: 75 + weight: 100 + prefix_list: + inbound: rmap1 + outbound: rmap2 + - afi: ipv6 + safi: unicast + - neighbor_address: 192.0.2.33 + address_family: + - afi: ipv4 + safi: multicast + inherit: + template: BasePolicy + sequence: 200 + vrfs: + - vrf: site-1 + neighbors: + - neighbor_address: 203.0.113.1 + address_family: + - afi: ipv4 + safi: unicast + suppress_inactive: True + next_hop_self: + set: True + - neighbor_address: 203.0.113.2 + address_family: + - afi: ipv6 + safi: unicast + - afi: ipv4 + safi: multicast + send_community: + set: True + state: rendered + +# Task Output (redacted) +# ----------------------- +# rendered: +# - router bgp 65536 +# - neighbor 192.0.2.32 +# - address-family ipv4 unicast +# - maximum-prefix 20 75 +# - weight 100 +# - prefix-list rmap1 in +# - prefix-list rmap2 out +# - address-family ipv6 unicast +# - neighbor 192.0.2.33 +# - address-family ipv4 multicast +# - inherit peer-policy BasePolicy 200 +# - vrf site-1 +# - neighbor 203.0.113.1 +# - address-family ipv4 unicast +# - suppress-inactive +# - next-hop-self +# - neighbor 203.0.113.2 +# - address-family ipv6 unicast +# - address-family ipv4 multicast +# - send-community + +# Using parsed + +# parsed.cfg +# ------------ +# router bgp 65536 +# neighbor 192.0.2.32 +# address-family ipv4 unicast +# maximum-prefix 20 75 +# weight 100 +# prefix-list rmap1 in +# prefix-list rmap2 out +# address-family ipv6 unicast +# neighbor 192.0.2.33 +# address-family ipv4 multicast +# inherit peer-policy BasePolicy 200 +# vrf site-1 +# neighbor 203.0.113.1 +# address-family ipv4 unicast +# suppress-inactive +# next-hop-self +# neighbor 203.0.113.2 +# address-family ipv4 multicast +# send-community +# address-family ipv6 unicast + +- name: Parse externally provided BGP neighbor AF config + register: result + cisco.nxos.nxos_bgp_neighbor_address_family: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# as_number: "65536" +# neighbors: +# - neighbor_address: 192.0.2.32 +# address_family: +# - afi: ipv4 +# safi: unicast +# maximum_prefix: +# max_prefix_limit: 20 +# generate_warning_threshold: 75 +# weight: 100 +# prefix_list: +# inbound: rmap1 +# outbound: rmap2 +# - afi: ipv6 +# safi: unicast +# - neighbor_address: 192.0.2.33 +# address_family: +# - afi: ipv4 +# safi: multicast +# inherit: +# template: BasePolicy +# sequence: 200 +# vrfs: +# - vrf: site-1 +# neighbors: +# - neighbor_address: 203.0.113.1 +# address_family: +# - afi: ipv4 +# safi: unicast +# suppress_inactive: true +# next_hop_self: +# set: true +# - neighbor_address: 203.0.113.2 +# address_family: +# - afi: ipv4 +# safi: multicast +# send_community: +# set: True +# - afi: ipv6 +# safi: unicast +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_neighbor_address_family.bgp_neighbor_address_family import ( + Bgp_neighbor_address_familyArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_neighbor_address_family.bgp_neighbor_address_family import ( + Bgp_neighbor_address_family, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Bgp_neighbor_address_familyArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Bgp_neighbor_address_family(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py new file mode 100644 index 00000000..cc5624ee --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py @@ -0,0 +1,781 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_bgp_neighbor_af +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2023-02-24) Manages BGP address-family's neighbors configuration. +description: +- Manages BGP address-family's neighbors configurations on NX-OS switches. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +deprecated: + alternative: nxos_bgp_neighbor_address_family + why: Updated module released with more functionality. + removed_at_date: '2023-02-24' +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- C(state=absent) removes the whole BGP address-family's neighbor configuration. +- Default, when supported, removes properties +- In order to default maximum-prefix configuration, only C(max_prefix_limit=default) + is needed. +options: + asn: + description: + - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or + ASDOT notation. + required: true + type: str + vrf: + description: + - Name of the VRF. The name 'default' is a valid VRF representing the global bgp. + default: default + type: str + neighbor: + description: + - Neighbor Identifier. Valid values are string. Neighbors may use IPv4 or IPv6 + notation, with or without prefix length. + required: true + type: str + afi: + description: + - Address Family Identifier. + required: true + choices: + - ipv4 + - ipv6 + - vpnv4 + - vpnv6 + - l2vpn + type: str + safi: + description: + - Sub Address Family Identifier. + required: true + choices: + - unicast + - multicast + - evpn + type: str + additional_paths_receive: + description: + - Valid values are enable for basic command enablement; disable for disabling + the command at the neighbor af level (it adds the disable keyword to the basic + command); and inherit to remove the command at this level (the command value + is inherited from a higher BGP layer). + choices: + - enable + - disable + - inherit + type: str + additional_paths_send: + description: + - Valid values are enable for basic command enablement; disable for disabling + the command at the neighbor af level (it adds the disable keyword to the basic + command); and inherit to remove the command at this level (the command value + is inherited from a higher BGP layer). + choices: + - enable + - disable + - inherit + type: str + advertise_map_exist: + description: + - Conditional route advertisement. This property requires two route maps, an advertise-map + and an exist-map. Valid values are an array specifying both the advertise-map + name and the exist-map name, or simply 'default' e.g. ['my_advertise_map', 'my_exist_map']. + This command is mutually exclusive with the advertise_map_non_exist property. + type: list + elements: str + advertise_map_non_exist: + description: + - Conditional route advertisement. This property requires two route maps, an advertise-map + and an exist-map. Valid values are an array specifying both the advertise-map + name and the non-exist-map name, or simply 'default' e.g. ['my_advertise_map', + 'my_non_exist_map']. This command is mutually exclusive with the advertise_map_exist + property. + type: list + elements: str + allowas_in: + description: + - Activate allowas-in property + type: bool + allowas_in_max: + description: + - Max-occurrences value for allowas_in. Valid values are an integer value or 'default'. + This is mutually exclusive with allowas_in. + type: str + as_override: + description: + - Activate the as-override feature. + type: bool + default_originate: + description: + - Activate the default-originate feature. + type: bool + default_originate_route_map: + description: + - Route-map for the default_originate property. Valid values are a string defining + a route-map name, or 'default'. This is mutually exclusive with default_originate. + type: str + disable_peer_as_check: + description: + - Disable checking of peer AS-number while advertising + type: bool + filter_list_in: + description: + - Valid values are a string defining a filter-list name, or 'default'. + type: str + filter_list_out: + description: + - Valid values are a string defining a filter-list name, or 'default'. + type: str + max_prefix_limit: + description: + - maximum-prefix limit value. Valid values are an integer value or 'default'. + type: str + max_prefix_interval: + description: + - Optional restart interval. Valid values are an integer. Requires max_prefix_limit. + May not be combined with max_prefix_warning. + type: str + max_prefix_threshold: + description: + - Optional threshold percentage at which to generate a warning. Valid values are + an integer value. Requires max_prefix_limit. + type: str + max_prefix_warning: + description: + - Optional warning-only keyword. Requires max_prefix_limit. May not be combined + with max_prefix_interval. + type: bool + next_hop_self: + description: + - Activate the next-hop-self feature. + type: bool + next_hop_third_party: + description: + - Activate the next-hop-third-party feature. + type: bool + prefix_list_in: + description: + - Valid values are a string defining a prefix-list name, or 'default'. + type: str + prefix_list_out: + description: + - Valid values are a string defining a prefix-list name, or 'default'. + type: str + route_map_in: + description: + - Valid values are a string defining a route-map name, or 'default'. + type: str + route_map_out: + description: + - Valid values are a string defining a route-map name, or 'default'. + type: str + route_reflector_client: + description: + - Router reflector client. + type: bool + send_community: + description: + - send-community attribute. + choices: + - none + - both + - extended + - standard + - default + type: str + soft_reconfiguration_in: + description: + - Valid values are 'enable' for basic command enablement; 'always' to add the + always keyword to the basic command; and 'inherit' to remove the command at + this level (the command value is inherited from a higher BGP layer). + choices: + - enable + - always + - inherit + type: str + soo: + description: + - Site-of-origin. Valid values are a string defining a VPN extcommunity or 'default'. + type: str + suppress_inactive: + description: + - suppress-inactive feature. + type: bool + unsuppress_map: + description: + - unsuppress-map. Valid values are a string defining a route-map name or 'default'. + type: str + weight: + description: + - Weight value. Valid values are an integer value or 'default'. + type: str + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: + - present + - absent + type: str + rewrite_evpn_rt_asn: + description: + - Auto generate route targets for EBGP neighbor. + type: bool + version_added: 1.1.0 +""" +EXAMPLES = """ +- name: configure RR client + cisco.nxos.nxos_bgp_neighbor_af: + asn: 65535 + neighbor: 192.0.2.3 + afi: ipv4 + safi: unicast + route_reflector_client: true + state: present + rewrite_evpn_rt_asn: true +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["router bgp 65535", "neighbor 192.0.2.3", + "address-family ipv4 unicast", "route-reflector-client", + "rewrite-evpn-rt-asn"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +BOOL_PARAMS = [ + "allowas_in", + "as_override", + "default_originate", + "disable_peer_as_check", + "next_hop_self", + "next_hop_third_party", + "route_reflector_client", + "suppress_inactive", + "rewrite_evpn_rt_asn", +] +PARAM_TO_COMMAND_KEYMAP = { + "afi": "address-family", + "asn": "router bgp", + "neighbor": "neighbor", + "additional_paths_receive": "capability additional-paths receive", + "additional_paths_send": "capability additional-paths send", + "advertise_map_exist": "advertise-map exist-map", + "advertise_map_non_exist": "advertise-map non-exist-map", + "allowas_in": "allowas-in", + "allowas_in_max": "allowas-in", + "as_override": "as-override", + "default_originate": "default-originate", + "default_originate_route_map": "default-originate route-map", + "disable_peer_as_check": "disable-peer-as-check", + "filter_list_in": "filter-list in", + "filter_list_out": "filter-list out", + "max_prefix_limit": "maximum-prefix", + "max_prefix_interval": "maximum-prefix interval", + "max_prefix_threshold": "maximum-prefix threshold", + "max_prefix_warning": "maximum-prefix warning", + "next_hop_self": "next-hop-self", + "next_hop_third_party": "next-hop-third-party", + "prefix_list_in": "prefix-list in", + "prefix_list_out": "prefix-list out", + "route_map_in": "route-map in", + "route_map_out": "route-map out", + "route_reflector_client": "route-reflector-client", + "safi": "address-family", + "send_community": "send-community", + "soft_reconfiguration_in": "soft-reconfiguration inbound", + "soo": "soo", + "suppress_inactive": "suppress-inactive", + "unsuppress_map": "unsuppress-map", + "weight": "weight", + "vrf": "vrf", + "rewrite_evpn_rt_asn": "rewrite-evpn-rt-asn", +} + + +def get_value(arg, config, module): + custom = [ + "additional_paths_send", + "additional_paths_receive", + "max_prefix_limit", + "max_prefix_interval", + "max_prefix_threshold", + "max_prefix_warning", + "send_community", + "soft_reconfiguration_in", + ] + command = PARAM_TO_COMMAND_KEYMAP[arg] + has_command = re.search(r"^\s+{0}\s*".format(command), config, re.M) + has_command_val = re.search(r"(?:{0}\s)(?P<value>.*)$".format(command), config, re.M) + value = "" + + if arg in custom: + value = get_custom_value(arg, config, module) + + elif arg == "next_hop_third_party": + has_no_command = re.search(r"^\s+no\s+{0}\s*$".format(command), config, re.M) + value = False + if not has_no_command: + value = True + + elif arg in BOOL_PARAMS: + value = False + if has_command: + value = True + + elif command.startswith("advertise-map"): + value = [] + has_adv_map = re.search( + r"{0}\s(?P<value1>.*)\s{1}\s(?P<value2>.*)$".format(*command.split()), + config, + re.M, + ) + if has_adv_map: + value = list(has_adv_map.groups()) + + elif command.split()[0] in ["filter-list", "prefix-list", "route-map"]: + has_cmd_direction_val = re.search( + r"{0}\s(?P<value>.*)\s{1}$".format(*command.split()), + config, + re.M, + ) + if has_cmd_direction_val: + value = has_cmd_direction_val.group("value") + + elif has_command_val: + value = has_command_val.group("value") + + return value + + +def get_custom_value(arg, config, module): + command = PARAM_TO_COMMAND_KEYMAP.get(arg) + splitted_config = config.splitlines() + value = "" + + if arg.startswith("additional_paths"): + value = "inherit" + for line in splitted_config: + if command in line: + if "disable" in line: + value = "disable" + else: + value = "enable" + elif arg.startswith("max_prefix"): + for line in splitted_config: + if "maximum-prefix" in line: + splitted_line = line.split() + if arg == "max_prefix_limit": + value = splitted_line[1] + elif arg == "max_prefix_interval" and "restart" in line: + value = splitted_line[-1] + elif arg == "max_prefix_threshold" and len(splitted_line) > 2: + try: + int(splitted_line[2]) + value = splitted_line[2] + except ValueError: + value = "" + elif arg == "max_prefix_warning": + value = "warning-only" in line + elif arg == "soft_reconfiguration_in": + value = "inherit" + for line in splitted_config: + if command in line: + if "always" in line: + value = "always" + else: + value = "enable" + + elif arg == "send_community": + value = "none" + for line in splitted_config: + if command in line: + if "extended" in line: + if value == "standard": + value = "both" + else: + value = "extended" + elif "both" in line: + value = "both" + else: + value = "standard" + + return value + + +def get_existing(module, args, warnings): + existing = {} + netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) + + asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S) + match_asn = asn_regex.match(str(netcfg)) + + if match_asn: + existing_asn = match_asn.group("existing_asn") + parents = ["router bgp {0}".format(existing_asn)] + + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + parents.append("neighbor {0}".format(module.params["neighbor"])) + parents.append("address-family {0} {1}".format(module.params["afi"], module.params["safi"])) + config = netcfg.get_section(parents) + + if config: + for arg in args: + if arg not in ["asn", "vrf", "neighbor", "afi", "safi"]: + existing[arg] = get_value(arg, config, module) + + existing["asn"] = existing_asn + existing["neighbor"] = module.params["neighbor"] + existing["vrf"] = module.params["vrf"] + existing["afi"] = module.params["afi"] + existing["safi"] = module.params["safi"] + else: + warnings.append("The BGP process didn't exist but the task just created it.") + + return existing + + +def apply_key_map(key_map, table): + new_dict = {} + for key in table: + new_key = key_map.get(key) + if new_key: + new_dict[new_key] = table.get(key) + + return new_dict + + +def get_default_command(key, value, existing_commands): + command = "" + if existing_commands.get(key): + existing_value = existing_commands.get(key) + if value == "inherit": + if existing_value != "inherit": + command = "no {0}".format(key) + else: + if key == "advertise-map exist-map": + command = "no advertise-map {0} exist-map {1}".format( + existing_value[0], + existing_value[1], + ) + elif key == "advertise-map non-exist-map": + command = "no advertise-map {0} non-exist-map {1}".format( + existing_value[0], + existing_value[1], + ) + elif key == "filter-list in": + command = "no filter-list {0} in".format(existing_value) + elif key == "filter-list out": + command = "no filter-list {0} out".format(existing_value) + elif key == "prefix-list in": + command = "no prefix-list {0} in".format(existing_value) + elif key == "prefix-list out": + command = "no prefix-list {0} out".format(existing_value) + elif key == "route-map in": + command = "no route-map {0} in".format(existing_value) + elif key == "route-map out": + command = "no route-map {0} out".format(existing_value) + elif key.startswith("maximum-prefix"): + command = "no maximum-prefix" + elif key == "allowas-in max": + command = ["no allowas-in {0}".format(existing_value)] + command.append("allowas-in") + else: + command = "no {0} {1}".format(key, existing_value) + else: + if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS: + command = "no {0}".format(key) + return command + + +def fix_proposed(module, existing, proposed): + allowas_in = proposed.get("allowas_in") + allowas_in_max = proposed.get("allowas_in_max") + + if allowas_in_max and not allowas_in: + proposed.pop("allowas_in_max") + elif allowas_in and allowas_in_max: + proposed.pop("allowas_in") + + if existing.get("send_community") == "none" and proposed.get("send_community") == "default": + proposed.pop("send_community") + return proposed + + +def state_present(module, existing, proposed, candidate): + commands = list() + proposed = fix_proposed(module, existing, proposed) + + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) + existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) + for key, value in proposed_commands.items(): + if value in ["inherit", "default"]: + command = get_default_command(key, value, existing_commands) + + if isinstance(command, str): + if command and command not in commands: + commands.append(command) + elif isinstance(command, list): + for cmd in command: + if cmd not in commands: + commands.append(cmd) + + elif key.startswith("maximum-prefix"): + if module.params["max_prefix_limit"] != "default": + command = "maximum-prefix {0}".format(module.params["max_prefix_limit"]) + if module.params["max_prefix_threshold"]: + command += " {0}".format(module.params["max_prefix_threshold"]) + if module.params["max_prefix_interval"]: + command += " restart {0}".format(module.params["max_prefix_interval"]) + elif module.params["max_prefix_warning"]: + command += " warning-only" + commands.append(command) + + elif value is True: + commands.append(key) + elif value is False: + commands.append("no {0}".format(key)) + elif key == "address-family": + commands.append( + "address-family {0} {1}".format(module.params["afi"], module.params["safi"]), + ) + elif key.startswith("capability additional-paths"): + command = key + if value == "disable": + command += " disable" + commands.append(command) + elif key.startswith("advertise-map"): + direction = key.split()[1] + commands.append("advertise-map {1} {0} {2}".format(direction, *value)) + elif key.split()[0] in ["filter-list", "prefix-list", "route-map"]: + commands.append("{1} {0} {2}".format(value, *key.split())) + + elif key == "soft-reconfiguration inbound": + command = "" + if value == "enable": + command = key + elif value == "always": + command = "{0} {1}".format(key, value) + commands.append(command) + elif key == "send-community": + command = key + if value in ["standard", "extended"]: + commands.append("no " + key + " both") + command += " {0}".format(value) + commands.append(command) + else: + command = "{0} {1}".format(key, value) + commands.append(command) + + if commands: + parents = ["router bgp {0}".format(module.params["asn"])] + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + parents.append("neighbor {0}".format(module.params["neighbor"])) + + af_command = "address-family {0} {1}".format(module.params["afi"], module.params["safi"]) + parents.append(af_command) + if af_command in commands: + commands.remove(af_command) + candidate.add(commands, parents=parents) + + +def state_absent(module, existing, candidate): + commands = [] + parents = ["router bgp {0}".format(module.params["asn"])] + if module.params["vrf"] != "default": + parents.append("vrf {0}".format(module.params["vrf"])) + + parents.append("neighbor {0}".format(module.params["neighbor"])) + commands.append("no address-family {0} {1}".format(module.params["afi"], module.params["safi"])) + candidate.add(commands, parents=parents) + + +def main(): + argument_spec = dict( + asn=dict(required=True, type="str"), + vrf=dict(required=False, type="str", default="default"), + neighbor=dict(required=True, type="str"), + afi=dict( + required=True, + type="str", + choices=["ipv4", "ipv6", "vpnv4", "vpnv6", "l2vpn"], + ), + safi=dict(required=True, type="str", choices=["unicast", "multicast", "evpn"]), + additional_paths_receive=dict( + required=False, + type="str", + choices=["enable", "disable", "inherit"], + ), + additional_paths_send=dict( + required=False, + type="str", + choices=["enable", "disable", "inherit"], + ), + advertise_map_exist=dict(required=False, type="list", elements="str"), + advertise_map_non_exist=dict(required=False, type="list", elements="str"), + allowas_in=dict(required=False, type="bool"), + allowas_in_max=dict(required=False, type="str"), + as_override=dict(required=False, type="bool"), + default_originate=dict(required=False, type="bool"), + default_originate_route_map=dict(required=False, type="str"), + disable_peer_as_check=dict(required=False, type="bool"), + filter_list_in=dict(required=False, type="str"), + filter_list_out=dict(required=False, type="str"), + max_prefix_limit=dict(required=False, type="str"), + max_prefix_interval=dict(required=False, type="str"), + max_prefix_threshold=dict(required=False, type="str"), + max_prefix_warning=dict(required=False, type="bool"), + next_hop_self=dict(required=False, type="bool"), + next_hop_third_party=dict(required=False, type="bool"), + prefix_list_in=dict(required=False, type="str"), + prefix_list_out=dict(required=False, type="str"), + route_map_in=dict(required=False, type="str"), + route_map_out=dict(required=False, type="str"), + route_reflector_client=dict(required=False, type="bool"), + send_community=dict( + required=False, + choices=["none", "both", "extended", "standard", "default"], + ), + soft_reconfiguration_in=dict( + required=False, + type="str", + choices=["enable", "always", "inherit"], + ), + soo=dict(required=False, type="str"), + suppress_inactive=dict(required=False, type="bool"), + unsuppress_map=dict(required=False, type="str"), + weight=dict(required=False, type="str"), + state=dict(choices=["present", "absent"], default="present", required=False), + rewrite_evpn_rt_asn=dict(required=False, type="bool"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ["advertise_map_exist", "advertise_map_non_exist"], + ["max_prefix_interval", "max_prefix_warning"], + ["default_originate", "default_originate_route_map"], + ["allowas_in", "allowas_in_max"], + ], + supports_check_mode=True, + ) + + warnings = list() + result = dict(changed=False, warnings=warnings) + + state = module.params["state"] + for key in [ + "max_prefix_interval", + "max_prefix_warning", + "max_prefix_threshold", + ]: + if module.params[key] and not module.params["max_prefix_limit"]: + module.fail_json(msg="max_prefix_limit is required when using %s" % key) + if module.params["vrf"] == "default" and module.params["soo"]: + module.fail_json(msg="SOO is only allowed in non-default VRF") + + args = PARAM_TO_COMMAND_KEYMAP.keys() + existing = get_existing(module, args, warnings) + + if existing.get("asn") and state == "present": + if existing.get("asn") != module.params["asn"]: + module.fail_json( + msg="Another BGP ASN already exists.", + proposed_asn=module.params["asn"], + existing_asn=existing.get("asn"), + ) + + for param in ["advertise_map_exist", "advertise_map_non_exist"]: + if module.params[param] == ["default"]: + module.params[param] = "default" + + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + + proposed = {} + for key, value in proposed_args.items(): + if key not in ["asn", "vrf", "neighbor"]: + if not isinstance(value, list): + if str(value).lower() == "true": + value = True + elif str(value).lower() == "false": + value = False + elif str(value).lower() == "default": + if key in BOOL_PARAMS: + value = False + else: + value = "default" + elif key == "send_community" and str(value).lower() == "none": + value = "default" + if existing.get(key) != value: + proposed[key] = value + + candidate = CustomNetworkConfig(indent=3) + if state == "present": + state_present(module, existing, proposed, candidate) + elif state == "absent" and existing: + state_absent(module, existing, candidate) + + if candidate: + candidate = candidate.items_text() + if not module.check_mode: + responses = load_config(module, candidate) + if responses: + for resp in responses: + if resp: + if resp.endswith("is valid only for EBGP peers"): + module.fail_json(msg=resp) + result["changed"] = True + result["commands"] = candidate + else: + result["commands"] = [] + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py new file mode 100644 index 00000000..7febbf8a --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_command +extends_documentation_fragment: +- cisco.nxos.nxos +author: Peter Sprygada (@privateip) +notes: +- Limited Support for Cisco MDS +short_description: Run arbitrary command on Cisco NXOS devices +description: +- Sends an arbitrary command to an NXOS node and returns the results read from the + device. This module includes an argument that will cause the module to wait for + a specific condition before returning or timing out if the condition is not met. +version_added: 1.0.0 +options: + commands: + description: + - The commands to send to the remote NXOS device. 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 retires as expired. + - The I(commands) argument also accepts an alternative form that allows for complex + values that specify the command to run and the output format to return. This + can be done on a command by command basis. The complex argument supports the + keywords C(command) and C(output) where C(command) is the command to run and + C(output) is one of 'text' or 'json'. + - If a command sent to the device requires answering a prompt, it is possible to pass + a dict containing command, answer and prompt. Common answers are 'y' or "\\r" + (carriage return, must be double quotes). See examples. + required: true + type: list + elements: raw + wait_for: + description: + - Specifies what to evaluate from the output of the command and what conditionals + to apply. This argument will cause the task to wait for a particular conditional + to be true before moving forward. If the conditional is not true by the configured + retries, the task fails. See examples. + aliases: + - waitfor + 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 I(wait_for) must be satisfied. If + the value is set to C(any) then only one of the values must be satisfied. + default: all + choices: ['any', 'all'] + type: str + retries: + description: + - Specifies the number of retries a command should by tried before it is considered + failed. The command is run on the target device every retry and evaluated against + the I(wait_for) conditionals. + - The commands are run once when I(retries) is set to C(0). + default: 9 + type: int + interval: + description: + - Configures the interval in seconds to wait between retries of the command. If + the command does not pass the specified conditional, the interval indicates + how to long to wait before trying the command again. + default: 1 + type: int +""" + +EXAMPLES = """ +- name: run show version on remote devices + cisco.nxos.nxos_command: + commands: show version + +- name: run show version and check to see if output contains Cisco + cisco.nxos.nxos_command: + commands: show version + wait_for: result[0] contains Cisco + +- name: run multiple commands on remote nodes + cisco.nxos.nxos_command: + commands: + - show version + - show interfaces + +- name: run multiple commands and evaluate the output + cisco.nxos.nxos_command: + commands: + - show version + - show interfaces + wait_for: + - result[0] contains Cisco + - result[1] contains loopback0 + +- name: run commands and specify the output format + cisco.nxos.nxos_command: + commands: + - command: show version + output: json + +- name: run commands that require answering a prompt + cisco.nxos.nxos_command: + commands: + - configure terminal + - command: no feature npv + prompt: Do you want to continue + answer: y + +""" + +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: ['...', '...'] +""" +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, + FailedConditionalError, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_lines, + transform_commands, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands + + +def parse_commands(module, warnings): + commands = transform_commands(module) + + if module.check_mode: + for item in list(commands): + if not item["command"].startswith("show"): + warnings.append( + "Only show commands are supported when using check mode, not " + "executing %s" % item["command"], + ) + commands.remove(item) + + return commands + + +def to_cli(obj): + cmd = obj["command"] + if obj.get("output") == "json": + cmd += " | json" + return cmd + + +def main(): + """entry point for module execution""" + argument_spec = dict( + # { command: <str>, output: <str>, prompt: <str>, response: <str> } + commands=dict(type="list", required=True, elements="raw"), + wait_for=dict(type="list", aliases=["waitfor"], elements="str"), + match=dict(default="all", choices=["any", "all"]), + retries=dict(default=9, type="int"), + interval=dict(default=1, type="int"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + result = {"changed": False, "warnings": warnings} + commands = parse_commands(module, warnings) + wait_for = module.params["wait_for"] or list() + conditionals = [] + + 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): + try: + if item(responses): + if match == "any": + conditionals = list() + break + conditionals.remove(item) + except FailedConditionalError as exc: + module.fail_json(msg=to_text(exc)) + + 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/cisco/nxos/plugins/modules/nxos_config.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_config.py new file mode 100644 index 00000000..45789124 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_config.py @@ -0,0 +1,579 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_config +extends_documentation_fragment: +- cisco.nxos.nxos +author: Peter Sprygada (@privateip) +short_description: Manage Cisco NXOS configuration sections +description: +- Cisco NXOS configurations use a simple block indent file syntax for segmenting configuration + into sections. This module provides an implementation for working with NXOS configuration + sections in a deterministic way. This module works with either CLI or NXAPI transports. +version_added: 1.0.0 +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-config to ensure idempotency + and correct diff. Be sure to note the configuration command syntax as some commands are + automatically modified by the device config parser. + type: list + aliases: + - commands + elements: str + 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: + - The I(src) argument provides a path to the configuration file to load into the + remote system. The path can either be a full system path to the configuration + file if the value starts with / or relative to the root of the implemented role + or playbook. This argument is mutually exclusive with the I(lines) and I(parents) + arguments. The configuration lines in the source file should be similar to how it + will appear if present in the running-configuration of the device including indentation + to ensure idempotency and correct diff. + type: path + replace_src: + description: + - The I(replace_src) argument provides path to the configuration file to load + into the remote system. This argument is used to replace the entire config with + a flat-file. This is used with argument I(replace) with value I(config). This + is mutually exclusive with the I(lines) and I(src) arguments. This argument + will only work for NX-OS versions that support `config replace`. Use I(nxos_file_copy) + module to copy the flat file to remote device and then use the path with this argument. + The configuration lines in the file should be similar to how it + will appear if present in the running-configuration of the device including the indentation + to ensure idempotency and correct diff. + type: str + 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 + match: + description: + - Instructs the module on the way to perform the matching of the set of commands + against the current device config. 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. Finally, if match is set to I(none), the module will not attempt + to compare the source configuration with the running configuration on the remote + device. + default: line + choices: + - line + - strict + - exact + - none + type: str + replace: + description: + - Instructs the module on the way to perform the 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. replace I(config) will only work for NX-OS versions + that support `config replace`. + default: line + choices: + - line + - block + - config + type: str + backup: + description: + - This argument will cause the module to create a full backup of the current C(running-config) + 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 or role root directory, if playbook is part of an ansible + role. If the directory does not exist, it is created. + type: bool + default: no + running_config: + description: + - The module, by default, will connect to the remote device and retrieve the current + running-config 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-config + for every task in a playbook. The I(running_config) argument allows the implementer + to pass in the configuration to use as the base config for comparison. + The configuration lines for this option should be similar to how it will appear if present + in the running-configuration of the device including the indentation to ensure idempotency + and correct diff. + aliases: + - config + type: str + defaults: + description: + - The I(defaults) argument will influence how the running-config is collected + from the device. When the value is set to true, the command used to collect + the running-config is append with the all keyword. When the value is set to + false, the command is issued without the all keyword + type: bool + default: no + save_when: + description: + - When changes are made to the device running-configuration, the changes are not + copied to non-volatile storage by default. Using this argument will change + that before. If the argument is set to I(always), then the running-config will + always be copied to the startup-config and the I(modified) flag will always + be set to True. If the argument is set to I(modified), then the running-config + will only be copied to the startup-config if it has changed since the last save + to startup-config. If the argument is set to I(never), the running-config will + never be copied to the startup-config. If the argument is set to I(changed), + then the running-config will only be copied to the startup-config if the task + has made a change. I(changed) was added in Ansible 2.6. + default: never + choices: + - always + - never + - modified + - changed + type: str + diff_against: + description: + - When using the C(ansible-playbook --diff) command line argument the module can + generate diffs against different sources. + - When this option is configure as I(startup), the module will return the diff + of the running-config against the startup-config. + - When this option is configured as I(intended), the module will return the diff + of the running-config against the configuration provided in the C(intended_config) + argument. + - When this option is configured as I(running), the module will return the before + and after diff of the running-config with respect to any changes made to the + device configuration. + choices: + - startup + - intended + - running + type: str + diff_ignore_lines: + description: + - Use this argument to specify one or more lines that should be ignored during + the diff. This is used for lines in the configuration that are automatically + updated by the system. This argument takes a list of regular expressions or + exact line matches. + type: list + elements: str + intended_config: + description: + - The C(intended_config) provides the master configuration that the node should + conform to and is used to check the final running-config against. This argument + will not modify any settings on the remote device and is strictly used to check + the compliance of the current device's configuration against. When specifying + this argument, the task should also modify the C(diff_against) value and set + it to I(intended). The configuration lines for this value should be similar to how it + will appear if present in the running-configuration of the device including the indentation + to ensure correct diff. + type: str + backup_options: + description: + - This is a dict object containing configurable options related to backup file + path. The value of this option is read only when C(backup) is set to I(True), + if C(backup) is set to I(false) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and + date in 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 will be stored. If the directory does not exist it will + be 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 in that case a I(backup) directory will be created in the current + working directory and backup configuration will be copied in C(filename) + within I(backup) directory. + type: path + type: dict +notes: +- Unsupported for Cisco MDS +- Abbreviated commands are NOT idempotent, see + U(https://docs.ansible.com/ansible/latest/network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands). +- To ensure idempotency and correct diff the configuration lines in the relevant module options should be similar to how they + appear if present in the running configuration on device including the indentation. +""" + +EXAMPLES = """ +- name: configure top level configuration and save it + cisco.nxos.nxos_config: + lines: hostname {{ inventory_hostname }} + save_when: modified + +- name: diff the running-config against a provided config + cisco.nxos.nxos_config: + diff_against: intended + intended_config: "{{ lookup('file', 'master.cfg') }}" + +- cisco.nxos.nxos_config: + lines: + - 10 permit ip 192.0.2.1/32 any log + - 20 permit ip 192.0.2.2/32 any log + - 30 permit ip 192.0.2.3/32 any log + - 40 permit ip 192.0.2.4/32 any log + - 50 permit ip 192.0.2.5/32 any log + parents: ip access-list test + before: no ip access-list test + match: exact + +- cisco.nxos.nxos_config: + lines: + - 10 permit ip 192.0.2.1/32 any log + - 20 permit ip 192.0.2.2/32 any log + - 30 permit ip 192.0.2.3/32 any log + - 40 permit ip 192.0.2.4/32 any log + parents: ip access-list test + before: no ip access-list test + replace: block + +- name: replace config with flat file + cisco.nxos.nxos_config: + replace_src: config.txt + replace: config + +- name: for idempotency, use full-form commands + cisco.nxos.nxos_config: + lines: + # - shut + - shutdown + # parents: int eth1/1 + parents: interface Ethernet1/1 + +- name: configurable backup path + cisco.nxos.nxos_config: + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'vlan 1', 'name default'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/nxos_config.2016-07-16@22:28:34 +filename: + description: The name of the backup file + returned: when backup is yes and filename is not specified in backup options + type: str + sample: nxos_config.2016-07-16@22:28:34 +shortname: + description: The full path to the backup file excluding the timestamp + returned: when backup is yes and filename is not specified in backup options + type: str + sample: /playbooks/ansible/backup/nxos_config +date: + description: The date extracted from the backup file name + returned: when backup is yes + type: str + sample: "2016-07-16" +time: + description: The time extracted from the backup file name + returned: when backup is yes + type: str + sample: "22:28:34" +""" +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, + dumps, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + get_connection, + load_config, + run_commands, +) + + +def get_running_config(module, config=None, flags=None): + contents = module.params["running_config"] + if not contents: + if config: + contents = config + else: + contents = get_config(module, flags=flags) + return contents + + +def get_candidate(module): + candidate = "" + if module.params["src"]: + if module.params["replace"] != "config": + candidate = module.params["src"] + if module.params["replace"] == "config": + candidate = "config replace {0}".format(module.params["replace_src"]) + elif module.params["lines"]: + candidate_obj = NetworkConfig(indent=2) + parents = module.params["parents"] or list() + candidate_obj.add(module.params["lines"], parents=parents) + candidate = dumps(candidate_obj, "raw") + return candidate + + +def execute_show_commands(module, commands, output="text"): + cmds = [] + for command in to_list(commands): + cmd = {"command": command, "output": output} + cmds.append(cmd) + body = run_commands(module, cmds) + return body + + +def save_config(module, result): + result["changed"] = True + if not module.check_mode: + cmd = { + "command": "copy running-config startup-config", + "output": "text", + } + run_commands(module, [cmd]) + else: + module.warn( + "Skipping command `copy running-config startup-config` " + "due to check_mode. Configuration not copied to " + "non-volatile storage", + ) + + +def main(): + """main entry point for module execution""" + backup_spec = dict(filename=dict(), dir_path=dict(type="path")) + argument_spec = dict( + src=dict(type="path"), + replace_src=dict(), + lines=dict(aliases=["commands"], type="list", elements="str"), + parents=dict(type="list", elements="str"), + before=dict(type="list", elements="str"), + after=dict(type="list", elements="str"), + match=dict(default="line", choices=["line", "strict", "exact", "none"]), + replace=dict(default="line", choices=["line", "block", "config"]), + running_config=dict(aliases=["config"]), + intended_config=dict(), + defaults=dict(type="bool", default=False), + backup=dict(type="bool", default=False), + backup_options=dict(type="dict", options=backup_spec), + save_when=dict(choices=["always", "never", "modified", "changed"], default="never"), + diff_against=dict(choices=["running", "startup", "intended"]), + diff_ignore_lines=dict(type="list", elements="str"), + ) + + mutually_exclusive = [("lines", "src", "replace_src"), ("parents", "src")] + + required_if = [ + ("match", "strict", ["lines"]), + ("match", "exact", ["lines"]), + ("replace", "block", ["lines"]), + ("replace", "config", ["replace_src"]), + ("diff_against", "intended", ["intended_config"]), + ] + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True, + ) + + warnings = list() + + result = {"changed": False, "warnings": warnings} + + config = None + + diff_ignore_lines = module.params["diff_ignore_lines"] + path = module.params["parents"] + connection = get_connection(module) + contents = None + flags = ["all"] if module.params["defaults"] else [] + replace_src = module.params["replace_src"] + if replace_src: + if module.params["replace"] != "config": + module.fail_json(msg="replace: config is required with replace_src") + + if module.params["backup"] or (module._diff and module.params["diff_against"] == "running"): + contents = get_config(module, flags=flags) + config = NetworkConfig(indent=2, contents=contents) + if module.params["backup"]: + result["__backup__"] = contents + + if any((module.params["src"], module.params["lines"], replace_src)): + match = module.params["match"] + replace = module.params["replace"] + + commit = not module.check_mode + candidate = get_candidate(module) + running = get_running_config(module, contents, flags=flags) + if replace_src: + commands = candidate.split("\n") + result["commands"] = result["updates"] = commands + if commit: + load_config(module, commands, replace=replace_src) + + result["changed"] = True + else: + try: + response = connection.get_diff( + candidate=candidate, + running=running, + diff_match=match, + diff_ignore_lines=diff_ignore_lines, + path=path, + diff_replace=replace, + ) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + config_diff = response["config_diff"] + if config_diff: + commands = config_diff.split("\n") + + if module.params["before"]: + commands[:0] = module.params["before"] + + if module.params["after"]: + commands.extend(module.params["after"]) + + result["commands"] = commands + result["updates"] = commands + + if commit: + load_config(module, commands, replace=replace_src) + + result["changed"] = True + + running_config = module.params["running_config"] + startup_config = None + + if module.params["save_when"] == "always": + save_config(module, result) + elif module.params["save_when"] == "modified": + output = execute_show_commands(module, ["show running-config", "show startup-config"]) + + running_config = NetworkConfig(indent=2, contents=output[0], ignore_lines=diff_ignore_lines) + startup_config = NetworkConfig(indent=2, contents=output[1], ignore_lines=diff_ignore_lines) + + if running_config.sha1 != startup_config.sha1: + save_config(module, result) + elif module.params["save_when"] == "changed" and result["changed"]: + save_config(module, result) + + if module._diff: + if not running_config: + output = execute_show_commands(module, "show running-config") + contents = output[0] + else: + contents = running_config + + # recreate the object in order to process diff_ignore_lines + running_config = NetworkConfig(indent=2, contents=contents, ignore_lines=diff_ignore_lines) + + if module.params["diff_against"] == "running": + if module.check_mode: + module.warn("unable to perform diff against running-config due to check mode") + contents = None + else: + contents = config.config_text + + elif module.params["diff_against"] == "startup": + if not startup_config: + output = execute_show_commands(module, "show startup-config") + contents = output[0] + else: + contents = startup_config.config_text + + elif module.params["diff_against"] == "intended": + contents = module.params["intended_config"] + + if contents is not None: + base_config = NetworkConfig(indent=2, contents=contents, ignore_lines=diff_ignore_lines) + + if running_config.sha1 != base_config.sha1: + before = "" + after = "" + if module.params["diff_against"] == "intended": + before = running_config + after = base_config + elif module.params["diff_against"] in ("startup", "running"): + before = base_config + after = running_config + + result.update( + { + "changed": True, + "diff": {"before": str(before), "after": str(after)}, + }, + ) + + if result.get("changed") and any((module.params["src"], module.params["lines"])): + msg = ( + "To ensure idempotency and correct diff the input configuration lines should be" + " similar to how they appear if present in" + " the running configuration on device" + ) + if module.params["src"]: + msg += " including the indentation" + if "warnings" in result: + result["warnings"].append(msg) + else: + result["warnings"] = msg + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py new file mode 100644 index 00000000..71d4ebb6 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py @@ -0,0 +1,550 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or +# https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +DOCUMENTATION = """ +module: nxos_devicealias +short_description: Configuration of device alias for Cisco NXOS MDS Switches. +description: +- Configuration of device alias for Cisco MDS NXOS. +version_added: 1.0.0 +author: +- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com) +notes: +- Tested against Cisco MDS NX-OS 8.4(1) +options: + distribute: + description: + - Enable/Disable device-alias distribution + type: bool + mode: + description: + - Mode of devices-alias, basic or enhanced + choices: + - basic + - enhanced + type: str + da: + description: + - List of device-alias to be added or removed + type: list + elements: dict + suboptions: + name: + description: + - Name of the device-alias to be added or removed + required: true + type: str + pwwn: + description: + - pwwn to which the name needs to be associated with + type: str + remove: + description: + - Removes the device-alias if set to True + type: bool + default: false + rename: + description: + - List of device-alias to be renamed + type: list + elements: dict + suboptions: + old_name: + description: + - Old name of the device-alias that needs to be renamed + required: true + type: str + new_name: + description: + - New name of the device-alias + required: true + type: str +""" + +EXAMPLES = """ +- name: Test that device alias module works + cisco.nxos.nxos_devicealias: + da: + - name: test1_add + pwwn: 56:2:22:11:22:88:11:67 + - name: test2_add + pwwn: 65:22:22:11:22:22:11:d + - name: dev1 + remove: true + - name: dev2 + remove: true + distribute: true + mode: enhanced + rename: + - new_name: bcd + old_name: abc + - new_name: bcd1 + old_name: abc1 + + +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: + - terminal dont-ask + - device-alias database + - device-alias name somename pwwn 10:00:00:00:89:a1:01:03 + - device-alias name somename1 pwwn 10:00:00:00:89:a1:02:03 + - device-alias commit + - no terminal dont-ask +""" + +import string + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +__metaclass__ = type + +VALID_DA_CHARS = ("-", "_", "$", "^") + + +class showDeviceAliasStatus(object): + """docstring for showDeviceAliasStatus""" + + def __init__(self, module): + self.module = module + self.distribute = "" + self.mode = "" + self.locked = False + self.update() + + def execute_show_cmd(self, cmd): + output = execute_show_command(cmd, self.module)[0] + return output + + def update(self): + command = "show device-alias status" + output = self.execute_show_cmd(command).split("\n") + for o in output: + if "Fabric Distribution" in o: + self.distribute = o.split(":")[1].strip().lower() + if "Mode" in o: + self.mode = o.split("Mode:")[1].strip().lower() + if "Locked" in o: + self.locked = True + + def isLocked(self): + return self.locked + + def getDistribute(self): + return self.distribute + + def getMode(self): + return self.mode + + +class showDeviceAliasDatabase(object): + """docstring for showDeviceAliasDatabase""" + + def __init__(self, module): + self.module = module + self.da_dict = {} + self.update() + + def execute_show_cmd(self, cmd): + output = execute_show_command(cmd, self.module)[0] + return output + + def update(self): + command = "show device-alias database" + # output = execute_show_command(command, self.module)[0].split("\n") + output = self.execute_show_cmd(command) + self.da_list = output.split("\n") + for eachline in self.da_list: + if "device-alias" in eachline: + sv = eachline.strip().split() + self.da_dict[sv[2]] = sv[4] + + def isNameInDaDatabase(self, name): + return name in self.da_dict.keys() + + def isPwwnInDaDatabase(self, pwwn): + newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")]) + return newpwwn in self.da_dict.values() + + def isNamePwwnPresentInDatabase(self, name, pwwn): + newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")]) + if name in self.da_dict.keys(): + if newpwwn == self.da_dict[name]: + return True + return False + + def getPwwnByName(self, name): + if name in self.da_dict.keys(): + return self.da_dict[name] + else: + return None + + def getNameByPwwn(self, pwwn): + newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")]) + for n, p in self.da_dict.items(): + if p == newpwwn: + return n + return None + + +def isPwwnValid(pwwn): + pwwnsplit = pwwn.split(":") + if len(pwwnsplit) != 8: + return False + for eachpwwnsplit in pwwnsplit: + if len(eachpwwnsplit) > 2 or len(eachpwwnsplit) < 1: + return False + if not all(c in string.hexdigits for c in eachpwwnsplit): + return False + return True + + +def isNameValid(name): + if not name[0].isalpha(): + # Illegal first character. Name must start with a letter + return False + if len(name) > 64: + return False + for character in name: + if not character.isalnum() and character not in VALID_DA_CHARS: + return False + return True + + +def execute_show_command(command, module, command_type="cli_show"): + output = "text" + commands = [{"command": command, "output": output}] + out = run_commands(module, commands) + return out + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def main(): + element_spec = dict( + name=dict(required=True, type="str"), + pwwn=dict(type="str"), + remove=dict(type="bool", default=False), + ) + + element_spec_rename = dict( + old_name=dict(required=True, type="str"), + new_name=dict(required=True, type="str"), + ) + + argument_spec = dict( + distribute=dict(type="bool"), + mode=dict(type="str", choices=["enhanced", "basic"]), + da=dict(type="list", elements="dict", options=element_spec), + rename=dict(type="list", elements="dict", options=element_spec_rename), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + messages = list() + commands_to_execute = list() + result = {"changed": False} + + distribute = module.params["distribute"] + mode = module.params["mode"] + da = module.params["da"] + rename = module.params["rename"] + + # Step 0.0: Validate syntax of name and pwwn + # Also validate syntax of rename arguments + if da is not None: + for eachdict in da: + name = eachdict["name"] + pwwn = eachdict["pwwn"] + remove = eachdict["remove"] + if pwwn is not None: + pwwn = pwwn.lower() + if not remove: + if pwwn is None: + module.fail_json( + msg="This device alias name " + + str(name) + + " which needs to be added, does not have pwwn specified. Please specify a valid pwwn", + ) + if not isNameValid(name): + module.fail_json( + msg="This pwwn name is invalid : " + + str(name) + + ". Note that name cannot be more than 64 alphanumeric chars, " + + "it must start with a letter, and can only contain these characters: " + + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]), + ) + if not isPwwnValid(pwwn): + module.fail_json( + msg="This pwwn is invalid : " + + str(pwwn) + + ". Please check that its a valid pwwn", + ) + if rename is not None: + for eachdict in rename: + oldname = eachdict["old_name"] + newname = eachdict["new_name"] + if not isNameValid(oldname): + module.fail_json( + msg="This pwwn name is invalid : " + + str(oldname) + + ". Note that name cannot be more than 64 alphanumeric chars, " + + "it must start with a letter, and can only contain these characters: " + + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]), + ) + if not isNameValid(newname): + module.fail_json( + msg="This pwwn name is invalid : " + + str(newname) + + ". Note that name cannot be more than 64 alphanumeric chars, " + + "it must start with a letter, and can only contain these characters: " + + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]), + ) + + # Step 0.1: Check DA status + shDAStausObj = showDeviceAliasStatus(module) + d = shDAStausObj.getDistribute() + m = shDAStausObj.getMode() + if shDAStausObj.isLocked(): + module.fail_json(msg="device-alias has acquired lock on the switch. Hence cannot procced.") + + # Step 1: Process distribute + commands = [] + if distribute is not None: + if distribute: + # playbook has distribute as True(enabled) + if d == "disabled": + # but switch distribute is disabled(false), so set it to + # true(enabled) + commands.append("device-alias distribute") + messages.append("device-alias distribute changed from disabled to enabled") + else: + messages.append( + "device-alias distribute remains unchanged. current distribution mode is enabled", + ) + else: + # playbook has distribute as False(disabled) + if d == "enabled": + # but switch distribute is enabled(true), so set it to + # false(disabled) + commands.append("no device-alias distribute") + messages.append("device-alias distribute changed from enabled to disabled") + else: + messages.append( + "device-alias distribute remains unchanged. current distribution mode is disabled", + ) + + cmds = flatten_list(commands) + if cmds: + commands_to_execute = commands_to_execute + cmds + if module.check_mode: + # Check mode implemented at the da_add/da_remove stage + pass + else: + result["changed"] = True + load_config(module, cmds) + + # Step 2: Process mode + commands = [] + if mode is not None: + if mode == "basic": + # playbook has mode as basic + if m == "enhanced": + # but switch mode is enhanced, so set it to basic + commands.append("no device-alias mode enhanced") + messages.append("device-alias mode changed from enhanced to basic") + else: + messages.append("device-alias mode remains unchanged. current mode is basic") + + else: + # playbook has mode as enhanced + if m == "basic": + # but switch mode is basic, so set it to enhanced + commands.append("device-alias mode enhanced") + messages.append("device-alias mode changed from basic to enhanced") + else: + messages.append("device-alias mode remains unchanged. current mode is enhanced") + + if commands: + if distribute: + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + else: + if distribute is None and d == "enabled": + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + + cmds = flatten_list(commands) + + if cmds: + commands_to_execute = commands_to_execute + cmds + if module.check_mode: + # Check mode implemented at the end + pass + else: + result["changed"] = True + load_config(module, cmds) + + # Step 3: Process da + commands = [] + shDADatabaseObj = showDeviceAliasDatabase(module) + if da is not None: + da_remove_list = [] + da_add_list = [] + for eachdict in da: + name = eachdict["name"] + pwwn = eachdict["pwwn"] + remove = eachdict["remove"] + if pwwn is not None: + pwwn = pwwn.lower() + if remove: + if shDADatabaseObj.isNameInDaDatabase(name): + commands.append("no device-alias name " + name) + da_remove_list.append(name) + else: + messages.append( + name + + " - This device alias name is not in switch device-alias database, hence cannot be removed.", + ) + else: + if shDADatabaseObj.isNamePwwnPresentInDatabase(name, pwwn): + messages.append( + name + + " : " + + pwwn + + " - This device alias name,pwwn is already in switch device-alias database, hence nothing to configure", + ) + else: + if shDADatabaseObj.isNameInDaDatabase(name): + module.fail_json( + msg=name + + " - This device alias name is already present in switch device-alias database but assigned to another pwwn (" + + shDADatabaseObj.getPwwnByName(name) + + ") hence cannot be added", + ) + + elif shDADatabaseObj.isPwwnInDaDatabase(pwwn): + module.fail_json( + msg=pwwn + + " - This device alias pwwn is already present in switch device-alias database but assigned to another name (" + + shDADatabaseObj.getNameByPwwn(pwwn) + + ") hence cannot be added", + ) + + else: + commands.append("device-alias name " + name + " pwwn " + pwwn) + da_add_list.append(name) + + if len(da_add_list) != 0 or len(da_remove_list) != 0: + commands = ["device-alias database"] + commands + if distribute: + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + else: + if distribute is None and d == "enabled": + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + + cmds = flatten_list(commands) + if cmds: + commands_to_execute = commands_to_execute + cmds + if module.check_mode: + # Check mode implemented at the end + pass + else: + result["changed"] = True + load_config(module, cmds) + if len(da_remove_list) != 0: + messages.append( + "the required device-alias were removed. " + ",".join(da_remove_list), + ) + if len(da_add_list) != 0: + messages.append( + "the required device-alias were added. " + ",".join(da_add_list), + ) + + # Step 5: Process rename + commands = [] + if rename is not None: + for eachdict in rename: + oldname = eachdict["old_name"] + newname = eachdict["new_name"] + if shDADatabaseObj.isNameInDaDatabase(newname): + module.fail_json( + changed=False, + commands=cmds, + msg=newname + + " - this name is already present in the device-alias database, hence we cannot rename " + + oldname + + " with this one", + ) + if shDADatabaseObj.isNameInDaDatabase(oldname): + commands.append("device-alias rename " + oldname + " " + newname) + else: + module.fail_json( + changed=False, + commands=cmds, + msg=oldname + + " - this name is not present in the device-alias database, hence we cannot rename.", + ) + + if len(commands) != 0: + commands = ["device-alias database"] + commands + if distribute: + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + else: + if distribute is None and d == "enabled": + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + cmds = flatten_list(commands) + if cmds: + commands_to_execute = commands_to_execute + cmds + if module.check_mode: + # Check mode implemented at the end + pass + else: + result["changed"] = True + load_config(module, cmds) + + # Step END: check for 'check' mode + if module.check_mode: + module.exit_json( + changed=False, + commands=commands_to_execute, + msg="Check Mode: No cmds issued to the hosts", + ) + + result["messages"] = messages + result["commands"] = commands_to_execute + result["warnings"] = warnings + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py new file mode 100644 index 00000000..83ef7839 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py @@ -0,0 +1,103 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_evpn_global +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Handles the EVPN control plane for VXLAN. +description: +- Handles the EVPN control plane for VXLAN. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +notes: +- This module is not supported on Nexus 3000 series of switches. +- Unsupported for Cisco MDS +options: + nv_overlay_evpn: + description: + - EVPN control plane. + required: true + type: bool +""" + +EXAMPLES = """ +- cisco.nxos.nxos_evpn_global: + nv_overlay_evpn: true +""" + +RETURN = """ +commands: + description: The set of commands to be sent to the remote device + returned: always + type: list + sample: ['nv overlay evpn'] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + get_config, + load_config, +) + + +def main(): + argument_spec = dict(nv_overlay_evpn=dict(required=True, type="bool")) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + result = {"changed": False} + + warnings = list() + if warnings: + result["warnings"] = warnings + + config = get_config(module) + commands = list() + + info = get_capabilities(module).get("device_info", {}) + os_platform = info.get("network_os_platform", "") + + if "3K" in os_platform: + module.fail_json(msg="This module is not supported on Nexus 3000 series") + + if module.params["nv_overlay_evpn"] is True: + if "nv overlay evpn" not in config: + commands.append("nv overlay evpn") + elif "nv overlay evpn" in config: + commands.append("no nv overlay evpn") + + if commands: + if not module.check_mode: + load_config(module, commands) + result["changed"] = True + + result["commands"] = commands + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py new file mode 100644 index 00000000..d4490bb7 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py @@ -0,0 +1,299 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_evpn_vni +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages Cisco EVPN VXLAN Network Identifier (VNI). +description: +- Manages Cisco Ethernet Virtual Private Network (EVPN) VXLAN Network Identifier (VNI) + configurations of a Nexus device. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- default, where supported, restores params default value. +- RD override is not permitted. You should set it to the default values first and + then reconfigure it. +- C(route_target_both), C(route_target_import) and C(route_target_export valid) values + are a list of extended communities, (i.e. ['1.2.3.4:5', '33:55']) or the keywords + 'auto' or 'default'. +- The C(route_target_both) property is discouraged due to the inconsistent behavior + of the property across Nexus platforms and image versions. For this reason it is + recommended to use explicit C(route_target_export) and C(route_target_import) properties + instead of C(route_target_both). +- RD valid values are a string in one of the route-distinguisher formats, the keyword + 'auto', or the keyword 'default'. +options: + vni: + description: + - The EVPN VXLAN Network Identifier. + required: true + type: str + route_distinguisher: + description: + - The VPN Route Distinguisher (RD). The RD is combined with the IPv4 or IPv6 prefix + learned by the PE router to create a globally unique address. + type: str + route_target_both: + description: + - Enables/Disables route-target settings for both import and export target communities + using a single property. + type: list + elements: str + route_target_import: + description: + - Sets the route-target 'import' extended communities. + type: list + elements: str + route_target_export: + description: + - Sets the route-target 'export' extended communities. + type: list + elements: str + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- name: vni configuration + cisco.nxos.nxos_evpn_vni: + vni: 6000 + route_distinguisher: 60:10 + route_target_import: + - 5000:10 + - 4100:100 + route_target_export: auto + route_target_both: default +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["evpn", "vni 6000 l2", "route-target import 5001:10"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +PARAM_TO_COMMAND_KEYMAP = { + "vni": "vni", + "route_distinguisher": "rd", + "route_target_both": "route-target both", + "route_target_import": "route-target import", + "route_target_export": "route-target export", +} + + +def get_value(arg, config, module): + command = PARAM_TO_COMMAND_KEYMAP.get(arg) + command_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M) + value = "" + if command in config: + value = command_re.search(config).group("value") + return value + + +def get_route_target_value(arg, config, module): + splitted_config = config.splitlines() + value_list = [] + command = PARAM_TO_COMMAND_KEYMAP.get(arg) + command_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M) + + for line in splitted_config: + value = "" + if command in line.strip(): + value = command_re.search(line).group("value") + value_list.append(value) + return value_list + + +def get_existing(module, args): + existing = {} + netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) + parents = ["evpn", "vni {0} l2".format(module.params["vni"])] + config = netcfg.get_section(parents) + + if config: + for arg in args: + if arg != "vni": + if arg == "route_distinguisher": + existing[arg] = get_value(arg, config, module) + else: + existing[arg] = get_route_target_value(arg, config, module) + + existing_fix = dict((k, v) for k, v in existing.items() if v) + if not existing_fix: + existing = existing_fix + + existing["vni"] = module.params["vni"] + + return existing + + +def apply_key_map(key_map, table): + new_dict = {} + for key in table: + new_key = key_map.get(key) + if new_key: + new_dict[new_key] = table.get(key) + return new_dict + + +def fix_proposed(proposed_commands): + new_proposed = {} + for key, value in proposed_commands.items(): + if key == "route-target both": + new_proposed["route-target export"] = value + new_proposed["route-target import"] = value + else: + new_proposed[key] = value + return new_proposed + + +def state_present(module, existing, proposed): + commands = list() + parents = list() + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) + existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) + + if proposed_commands.get("route-target both"): + proposed_commands = fix_proposed(proposed_commands) + + for key, value in proposed_commands.items(): + if key.startswith("route-target"): + if value == ["default"]: + existing_value = existing_commands.get(key) + + if existing_value: + for target in existing_value: + commands.append("no {0} {1}".format(key, target)) + elif not isinstance(value, list): + value = [value] + + for target in value: + if target == "default": + continue + if existing: + if target not in existing.get(key.replace("-", "_").replace(" ", "_")): + commands.append("{0} {1}".format(key, target)) + else: + commands.append("{0} {1}".format(key, target)) + + if existing.get(key.replace("-", "_").replace(" ", "_")): + for exi in existing.get(key.replace("-", "_").replace(" ", "_")): + if exi not in value: + commands.append("no {0} {1}".format(key, exi)) + + elif value == "default": + existing_value = existing_commands.get(key) + if existing_value: + commands.append("no {0} {1}".format(key, existing_value)) + else: + command = "{0} {1}".format(key, value) + commands.append(command) + + if commands: + parents = ["evpn", "vni {0} l2".format(module.params["vni"])] + + return commands, parents + + +def state_absent(module, existing, proposed): + commands = ["no vni {0} l2".format(module.params["vni"])] + parents = ["evpn"] + return commands, parents + + +def main(): + argument_spec = dict( + vni=dict(required=True, type="str"), + route_distinguisher=dict(required=False, type="str"), + route_target_both=dict(required=False, type="list", elements="str"), + route_target_import=dict(required=False, type="list", elements="str"), + route_target_export=dict(required=False, type="list", elements="str"), + state=dict(choices=["present", "absent"], default="present", required=False), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = dict(changed=False, warnings=warnings) + + state = module.params["state"] + args = PARAM_TO_COMMAND_KEYMAP.keys() + existing = get_existing(module, args) + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + commands = [] + parents = [] + + proposed = {} + for key, value in proposed_args.items(): + if key != "vni": + if value == "true": + value = True + elif value == "false": + value = False + if existing.get(key) != value: + proposed[key] = value + + if state == "present": + commands, parents = state_present(module, existing, proposed) + elif state == "absent" and existing: + commands, parents = state_absent(module, existing, proposed) + + if commands: + candidate = CustomNetworkConfig(indent=3) + candidate.add(commands, parents=parents) + candidate = candidate.items_text() + if not module.check_mode: + load_config(module, candidate) + results["changed"] = True + results["commands"] = candidate + else: + results["commands"] = [] + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py new file mode 100644 index 00000000..24e0dad2 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py @@ -0,0 +1,268 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_facts +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Gets facts about NX-OS switches +description: +- Collects facts from Cisco Nexus devices running the NX-OS operating system. Fact + collection is supported over both C(network_cli) and C(httpapi). This module prepends + all of the base network fact keys with C(ansible_net_<fact>). The facts module + will always collect a base set of facts from the device and can enable or disable + collection of additional facts. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +options: + gather_subset: + description: + - When supplied, this argument will gather operational facts only for the given subset. Possible + values for this argument include C(all), C(hardware), C(config), C(legacy), C(interfaces), and C(min). Can + specify a list of values to include a larger subset. Values can also be used + with an initial C(!) to specify that a specific subset should not be collected. + required: false + default: 'min' + type: list + elements: str + gather_network_resources: + description: + - When supplied, this argument will gather configuration facts only for the given subset. + Can specify a list of values to include a larger subset. Values can + also be used with an initial C(!) to specify that a specific subset should + not be collected. + - Valid subsets are C(all), C(bfd_interfaces), C(lag_interfaces), + C(telemetry), C(vlans), C(lacp), C(lacp_interfaces), C(interfaces), C(l3_interfaces), + C(l2_interfaces), C(lldp_global), C(acls), C(acl_interfaces), C(ospfv2), C(ospfv3), C(ospf_interfaces), + C(bgp_global), C(bgp_address_family), C(route_maps), C(prefix_lists), C(logging_global), C(ntp_global), + C(snmp_server), C(hostname). + required: false + type: list + elements: str + available_network_resources: + description: When set to C(true) a list of network resources for which resource modules are available will be provided. + type: bool + default: false +""" + +EXAMPLES = """ +- name: Gather all legacy facts + cisco.nxos.nxos_facts: + gather_subset: all +- name: Gather only the config and default facts + cisco.nxos.nxos_facts: + gather_subset: + - config +- name: Do not gather hardware facts + cisco.nxos.nxos_facts: + gather_subset: + - '!hardware' +- name: Gather legacy and resource facts + cisco.nxos.nxos_facts: + gather_subset: all + gather_network_resources: all +- name: Gather only the interfaces resource facts and no legacy facts + cisco.nxos.nxos_facts: + gather_subset: + - '!all' + - '!min' + gather_network_resources: + - interfaces +- name: Gather interfaces resource and minimal legacy facts + cisco.nxos.nxos_facts: + gather_subset: min + gather_network_resources: interfaces +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list +ansible_net_gather_network_resources: + description: The list of fact for network resource subsets collected from the device + returned: when the resource is configured + type: list +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_image: + description: The image file the device is running + returned: always + type: str +ansible_net_api: + description: The name of the transport + returned: always + type: str +ansible_net_license_hostid: + description: The License host id of the device + returned: always + type: str +ansible_net_python_version: + description: The Python version Ansible controller is using + returned: always + type: str +# hardware +ansible_net_filesystems: + description: All file system names available on the device + returned: when hardware is configured + type: list +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: + - The list of LLDP and CDP neighbors from the device. If both, + CDP and LLDP neighbor data is present on one port, CDP is preferred. + returned: when interfaces is configured + type: dict +# legacy (pre Ansible 2.2) +fan_info: + description: A hash of facts about fans in the remote device + returned: when legacy is configured + type: dict +hostname: + description: The configured hostname of the remote device + returned: when legacy is configured + type: dict +interfaces_list: + description: The list of interface names on the remote device + returned: when legacy is configured + type: dict +kickstart: + description: The software version used to boot the system + returned: when legacy is configured + type: str +module: + description: A hash of facts about the modules in a remote device + returned: when legacy is configured + type: dict +platform: + description: The hardware platform reported by the remote device + returned: when legacy is configured + type: str +power_supply_info: + description: A hash of facts about the power supplies in the remote device + returned: when legacy is configured + type: str +vlan_list: + description: The list of VLAN IDs configured on the remote device + returned: when legacy is configured + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.facts.facts import ( + FactsArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts + + +def get_chassis_type(connection): + """Return facts resource subsets based on + chassis model. + """ + target_type = "nexus" + + device_info = connection.get_device_info() + model = device_info.get("network_os_model", "") + platform = device_info.get("network_os_platform", "") + + if platform.startswith("DS-") and "MDS" in model: + target_type = "mds" + + return target_type + + +def main(): + """ + Main entry point for module execution + + :returns: ansible_facts + """ + argument_spec = FactsArgs.argument_spec + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + connection = Connection(module._socket_path) + facts = Facts(module, chassis_type=get_chassis_type(connection)) + + warnings = [] + + ansible_facts = {} + if module.params.get("available_network_resources"): + ansible_facts["available_network_resources"] = sorted(facts.get_resource_subsets().keys()) + + result = facts.get_facts() + additional_facts, additional_warnings = result + ansible_facts.update(additional_facts) + warnings.extend(additional_warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py new file mode 100644 index 00000000..c9229c4d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_feature +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manage features in NX-OS switches. +notes: +- Tested against Cisco MDS NX-OS 9.2(2) +description: +- Offers ability to enable and disable features in NX-OS. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +- Suhas Bharadwaj (@srbharadwaj) +options: + feature: + description: + - Name of feature. + required: true + type: str + state: + description: + - Desired state of the feature. + required: false + default: enabled + choices: + - enabled + - disabled + type: str +""" + +EXAMPLES = """ +- name: Ensure lacp is enabled + cisco.nxos.nxos_feature: + feature: lacp + state: enabled + +- name: Ensure ospf is disabled + cisco.nxos.nxos_feature: + feature: ospf + state: disabled + +- name: Ensure vpc is enabled + cisco.nxos.nxos_feature: + feature: vpc + state: enabled +""" + +RETURN = """ +commands: + description: The set of commands to be sent to the remote device + returned: always + type: list + sample: ['nv overlay evpn'] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + get_config, + load_config, + run_commands, +) + + +def get_available_features(feature, module): + available_features = {} + feature_regex = r"(?P<feature>\S+)\s+\d+\s+(?P<state>.*)" + command = {"command": "show feature", "output": "text"} + + try: + body = run_commands(module, [command])[0] + split_body = body.splitlines() + except (KeyError, IndexError): + return {} + + for line in split_body: + try: + match_feature = re.match(feature_regex, line, re.DOTALL) + feature_group = match_feature.groupdict() + feature = feature_group["feature"] + state = feature_group["state"] + except AttributeError: + feature = "" + state = "" + + if feature and state: + if "enabled" in state: + state = "enabled" + + if feature not in available_features: + available_features[feature] = state + else: + if available_features[feature] == "disabled" and state == "enabled": + available_features[feature] = state + + # certain configurable features do not + # show up in the output of "show feature" + # but appear in running-config when set + run_cfg = get_config(module, flags=["| include ^feature"]) + for item in re.findall(r"feature\s(.*)", run_cfg): + if item not in available_features: + available_features[item] = "enabled" + + if "fabric forwarding" not in available_features: + available_features["fabric forwarding"] = "disabled" + + return available_features + + +def get_commands(proposed, existing, state, module): + feature = validate_feature(module, mode="config") + commands = [] + feature_check = proposed == existing + if not feature_check: + if state == "enabled": + command = "feature {0}".format(feature) + commands.append(command) + elif state == "disabled": + command = "no feature {0}".format(feature) + commands.append(command) + return commands + + +def get_mds_mapping_features(): + feature_to_be_mapped = { + "show": { + "fcrxbbcredit": "extended_credit", + "port-track": "port_track", + "scp-server": "scpServer", + "sftp-server": "sftpServer", + "ssh": "sshServer", + "tacacs+": "tacacs", + "telnet": "telnetServer", + }, + "config": { + "extended_credit": "fcrxbbcredit", + "port_track": "port-track", + "scpServer": "scp-server", + "sftpServer": "sftp-server", + "sshServer": "ssh", + "tacacs": "tacacs+", + "telnetServer": "telnet", + }, + } + return feature_to_be_mapped + + +def validate_feature(module, mode="show"): + """Some features may need to be mapped due to inconsistency + between how they appear from "show feature" output and + how they are configured""" + + feature = module.params["feature"] + + try: + info = get_capabilities(module) + device_info = info.get("device_info", {}) + os_version = device_info.get("network_os_version", "") + os_platform = device_info.get("network_os_platform", "") + except ConnectionError: + os_version = "" + os_platform = "" + + if "8.1" in os_version: + feature_to_be_mapped = { + "show": { + "nv overlay": "nve", + "vn-segment-vlan-based": "vnseg_vlan", + "hsrp": "hsrp_engine", + "fabric multicast": "fabric_mcast", + "scp-server": "scpServer", + "sftp-server": "sftpServer", + "sla responder": "sla_responder", + "sla sender": "sla_sender", + "ssh": "sshServer", + "tacacs+": "tacacs", + "telnet": "telnetServer", + "ethernet-link-oam": "elo", + }, + "config": { + "nve": "nv overlay", + "vnseg_vlan": "vn-segment-vlan-based", + "hsrp_engine": "hsrp", + "fabric_mcast": "fabric multicast", + "scpServer": "scp-server", + "sftpServer": "sftp-server", + "sla_sender": "sla sender", + "sla_responder": "sla responder", + "sshServer": "ssh", + "tacacs": "tacacs+", + "telnetServer": "telnet", + "elo": "ethernet-link-oam", + }, + } + else: + feature_to_be_mapped = { + "show": { + "nv overlay": "nve", + "vn-segment-vlan-based": "vnseg_vlan", + "hsrp": "hsrp_engine", + "fabric multicast": "fabric_mcast", + "scp-server": "scpServer", + "sftp-server": "sftpServer", + "sla responder": "sla_responder", + "sla sender": "sla_sender", + "ssh": "sshServer", + "tacacs+": "tacacs", + "telnet": "telnetServer", + "ethernet-link-oam": "elo", + "port-security": "eth_port_sec", + }, + "config": { + "nve": "nv overlay", + "vnseg_vlan": "vn-segment-vlan-based", + "hsrp_engine": "hsrp", + "fabric_mcast": "fabric multicast", + "scpServer": "scp-server", + "sftpServer": "sftp-server", + "sla_sender": "sla sender", + "sla_responder": "sla responder", + "sshServer": "ssh", + "tacacs": "tacacs+", + "telnetServer": "telnet", + "elo": "ethernet-link-oam", + "eth_port_sec": "port-security", + }, + } + + if os_platform.startswith("DS-"): + feature_to_be_mapped = get_mds_mapping_features() + + if feature in feature_to_be_mapped[mode]: + feature = feature_to_be_mapped[mode][feature] + + return feature + + +def main(): + argument_spec = dict( + feature=dict(type="str", required=True), + state=dict(choices=["enabled", "disabled"], default="enabled"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = dict(changed=False, warnings=warnings) + + feature = validate_feature(module) + state = module.params["state"].lower() + + available_features = get_available_features(feature, module) + if feature not in available_features: + module.fail_json( + msg="Invalid feature name.", + features_currently_supported=available_features, + invalid_feature=feature, + ) + else: + existstate = available_features[feature] + + existing = dict(state=existstate) + proposed = dict(state=state) + results["changed"] = False + + cmds = get_commands(proposed, existing, state, module) + + if cmds: + # On N35 A8 images, some features return a yes/no prompt + # on enablement or disablement. Bypass using terminal dont-ask + cmds.insert(0, "terminal dont-ask") + if not module.check_mode: + load_config(module, cmds) + results["changed"] = True + + results["commands"] = cmds + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py new file mode 100644 index 00000000..4847a446 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py @@ -0,0 +1,497 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_file_copy +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Copy a file to a remote NXOS device. +description: +- This module supports two different workflows for copying a file to flash (or bootflash) + on NXOS devices. Files can either be (1) pushed from the Ansible controller to + the device or (2) pulled from a remote SCP file server to the device. File copies + are initiated from the NXOS device to the remote SCP server. This module only supports + the use of connection C(network_cli) or C(Cli) transport with connection C(local). +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +- Rewritten as a plugin by (@mikewiebe) +notes: +- Tested against NXOS 7.0(3)I2(5), 7.0(3)I4(6), 7.0(3)I5(3), 7.0(3)I6(1), 7.0(3)I7(3), + 6.0(2)A8(8), 7.0(3)F3(4), 7.3(0)D1(1), 8.3(0), 9.2, 9.3 +- Limited Support for Cisco MDS +- When pushing files (file_pull is False) to the NXOS device, feature scp-server must + be enabled. +- When pulling files (file_pull is True) to the NXOS device, feature scp-server is + not required. +- When pulling files (file_pull is True) to the NXOS device, no transfer will take + place if the file is already present. +- Check mode will tell you if the file would be copied. +requirements: +- paramiko or libssh (required when file_pull is False) +- scp (required when file_pull is False) +options: + local_file: + description: + - When (file_pull is False) this is the path to the local file on the Ansible + controller. The local directory must exist. + - When (file_pull is True) this is the target file name on the NXOS device. + type: path + remote_file: + description: + - When (file_pull is False) this is the remote file path on the NXOS device. If + omitted, the name of the local file will be used. The remote directory must + exist. + - When (file_pull is True) this is the full path to the file on the remote SCP + server to be copied to the NXOS device. + type: path + file_system: + description: + - The remote file system on the nxos device. If omitted, devices that support + a I(file_system) parameter will use their default values. + default: 'bootflash:' + type: str + connect_ssh_port: + description: + - B(Deprecated) + - This option has been deprecated and will be removed in a release after 2024-06-01. + - To maintain backwards compatibility, this option will continue to override the value of I(ansible_port) until removed. + - HORIZONTALLINE + - SSH server port used for file transfer. + - Only used when I(file_pull) is C(True). + default: 22 + type: int + file_pull: + description: + - When (False) file is copied from the Ansible controller to the NXOS device. + - When (True) file is copied from a remote SCP server to the NXOS device. In this + mode, the file copy is initiated from the NXOS device. + - If the file is already present on the device it will be overwritten and therefore + the operation is NOT idempotent. + type: bool + default: false + file_pull_protocol: + description: + - When file_pull is True, this can be used to define the transfer protocol for + copying file from remote to the NXOS device. + - When (file_pull is False), this is not used. + default: 'scp' + choices: + - scp + - sftp + - ftp + - http + - https + - tftp + type: str + file_pull_compact: + description: + - When file_pull is True, this is used to compact nxos image files. This option + can only be used with nxos image files. + - When (file_pull is False), this is not used. + type: bool + default: false + file_pull_kstack: + description: + - When file_pull is True, this can be used to speed up file copies when the nxos + running image supports the use-kstack option. + - When (file_pull is False), this is not used. + type: bool + default: false + local_file_directory: + description: + - When (file_pull is True) file is copied from a remote SCP server to the NXOS + device, and written to this directory on the NXOS device. If the directory does + not exist, it will be created under the file_system. This is an optional parameter. + - When (file_pull is False), this is not used. + type: path + file_pull_timeout: + description: + - B(Deprecated) + - This option has been deprecated and will be removed in a release after 2024-06-01. + - To maintain backwards compatibility, this option will continue to override the value of I(ansible_command_timeout) until removed. + - HORIZONTALLINE + - Use this parameter to set timeout in seconds, when transferring large files + or when the network is slow. + - When (file_pull is False), this is not used. + default: 300 + type: int + remote_scp_server: + description: + - The remote scp server address when file_pull is True. This is required if file_pull + is True. + - When (file_pull is False), this is not used. + type: str + remote_scp_server_user: + description: + - The remote scp server username when file_pull is True. This is required if file_pull + is True. + - When (file_pull is False), this is not used. + type: str + remote_scp_server_password: + description: + - The remote scp server password when file_pull is True. This is required if file_pull + is True. + - When (file_pull is False), this is not used. + type: str + vrf: + description: + - The VRF used to pull the file. Useful when no vrf management is defined. + - This option is not applicable for MDS switches. + default: management + type: str +""" + +EXAMPLES = """ +# File copy from ansible controller to nxos device +- name: copy from server to device + cisco.nxos.nxos_file_copy: + local_file: ./test_file.txt + remote_file: test_file.txt + +# Initiate file copy from the nxos device to transfer file from an SCP server back to the nxos device +- name: initiate file copy from device + cisco.nxos.nxos_file_copy: + file_pull: true + local_file: xyz + local_file_directory: dir1/dir2/dir3 + remote_file: /mydir/abc + remote_scp_server: 192.168.0.1 + remote_scp_server_user: myUser + remote_scp_server_password: myPassword + vrf: management + +# Initiate file copy from the nxos device to transfer file from a ftp server back to the nxos device. +# remote_scp_server_user and remote_scp_server_password are used to login to the FTP server. +- name: initiate file copy from device + cisco.nxos.nxos_file_copy: + file_pull: true + file_pull_protocol: ftp + local_file: xyz + remote_file: /mydir/abc + remote_scp_server: 192.168.0.1 + remote_scp_server_user: myUser + remote_scp_server_password: myPassword + vrf: management +""" + +RETURN = """ +transfer_status: + description: Whether a file was transferred to the nxos device. + returned: success + type: str + sample: 'Sent' +local_file: + description: The path of the local file. + returned: success + type: str + sample: '/path/to/local/file' +remote_file: + description: The path of the remote file. + returned: success + type: str + sample: '/path/to/remote/file' +remote_scp_server: + description: The name of the scp server when file_pull is True. + returned: success + type: str + sample: 'fileserver.example.com' +changed: + description: Indicates whether or not the file was copied. + returned: success + type: bool + sample: true +""" + +import hashlib +import os +import re + +from ansible.module_utils._text import to_bytes, to_text +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network import ( + get_resource_connection, +) + + +class FileCopy: + def __init__(self, module): + self._module = module + self._connection = get_resource_connection(self._module) + device_info = self._connection.get_device_info() + self._model = device_info.get("network_os_model", "") + self._platform = device_info.get("network_os_platform", "") + + +class FilePush(FileCopy): + def __init__(self, module): + super(FilePush, self).__init__(module) + self.result = {} + + def md5sum_check(self, dst, file_system): + command = "show file {0}{1} md5sum".format(file_system, dst) + remote_filehash = self._connection.run_commands(command)[0] + remote_filehash = to_bytes(remote_filehash, errors="surrogate_or_strict") + + local_file = self._module.params["local_file"] + try: + with open(local_file, "rb") as f: + filecontent = f.read() + except (OSError, IOError) as exc: + self._module.fail_json("Error reading the file: {0}".format(to_text(exc))) + + filecontent = to_bytes(filecontent, errors="surrogate_or_strict") + local_filehash = hashlib.md5(filecontent).hexdigest() + + decoded_rhash = remote_filehash.decode("UTF-8") + + if local_filehash == decoded_rhash: + return True + else: + return False + + def remote_file_exists(self, remote_file, file_system): + command = "dir {0}/{1}".format(file_system, remote_file) + body = self._connection.run_commands(command)[0] + + if "No such file" in body: + return False + else: + return self.md5sum_check(remote_file, file_system) + + def get_flash_size(self, file_system): + command = "dir {0}".format(file_system) + body = self._connection.run_commands(command)[0] + + match = re.search(r"(\d+) bytes free", body) + if match: + bytes_free = match.group(1) + return int(bytes_free) + + match = re.search(r"No such file or directory", body) + if match: + self._module.fail_json("Invalid nxos filesystem {0}".format(file_system)) + else: + self._module.fail_json("Unable to determine size of filesystem {0}".format(file_system)) + + def enough_space(self, file, file_system): + flash_size = self.get_flash_size(file_system) + file_size = os.path.getsize(file) + if file_size > flash_size: + return False + + return True + + def transfer_file_to_device(self, remote_file): + local_file = self._module.params["local_file"] + file_system = self._module.params["file_system"] + + if not self.enough_space(local_file, file_system): + self._module.fail_json("Could not transfer file. Not enough space on device.") + + # frp = full_remote_path, flp = full_local_path + frp = remote_file + if not file_system.startswith("bootflash:"): + frp = "{0}{1}".format(file_system, remote_file) + flp = os.path.join(os.path.abspath(local_file)) + + try: + self._connection.copy_file( + source=flp, + destination=frp, + proto="scp", + timeout=self._connection.get_option("persistent_command_timeout"), + ) + self.result["transfer_status"] = "Sent: File copied to remote device." + except Exception as exc: + self.result["failed"] = True + self.result["msg"] = "Exception received : %s" % exc + + def run(self): + local_file = self._module.params["local_file"] + remote_file = self._module.params["remote_file"] or os.path.basename(local_file) + file_system = self._module.params["file_system"] + + if not os.path.isfile(local_file): + self._module.fail_json("Local file {0} not found".format(local_file)) + + remote_file = remote_file or os.path.basename(local_file) + remote_exists = self.remote_file_exists(remote_file, file_system) + + if not remote_exists: + self.result["changed"] = True + file_exists = False + else: + self.result["transfer_status"] = "No Transfer: File already copied to remote device." + file_exists = True + + if not self._module.check_mode and not file_exists: + self.transfer_file_to_device(remote_file) + + self.result["local_file"] = local_file + if remote_file is None: + remote_file = os.path.basename(local_file) + self.result["remote_file"] = remote_file + self.result["file_system"] = file_system + + return self.result + + +class FilePull(FileCopy): + def __init__(self, module): + super(FilePull, self).__init__(module) + self.result = {} + + def mkdir(self, directory): + local_dir_root = "/" + dir_array = directory.split("/") + for each in dir_array: + if each: + mkdir_cmd = "mkdir " + local_dir_root + each + self._connection.run_commands(mkdir_cmd) + local_dir_root += each + "/" + return local_dir_root + + def copy_file_from_remote(self, local, local_file_directory, file_system): + # Build copy command components that will be used to initiate copy from the nxos device. + cmdroot = "copy " + self._module.params["file_pull_protocol"] + "://" + ruser = self._module.params["remote_scp_server_user"] + "@" + rserver = self._module.params["remote_scp_server"] + rserverpassword = self._module.params["remote_scp_server_password"] + rfile = self._module.params["remote_file"] + " " + if not rfile.startswith("/"): + rfile = "/" + rfile + + if not self._platform.startswith("DS-") and "MDS" not in self._model: + vrf = " vrf " + self._module.params["vrf"] + else: + vrf = "" + if self._module.params["file_pull_compact"]: + compact = " compact " + else: + compact = "" + if self._module.params["file_pull_kstack"]: + kstack = " use-kstack " + else: + kstack = "" + + # Create local file directory under NX-OS filesystem if + # local_file_directory playbook parameter is set. + local_dir_root = "/" + if local_file_directory: + local_dir_root = self.mkdir(local_file_directory) + + copy_cmd = ( + cmdroot + + ruser + + rserver + + rfile + + file_system + + local_dir_root + + local + + compact + + vrf + + kstack + ) + + self.result["copy_cmd"] = copy_cmd + pulled = self._connection.pull_file(command=copy_cmd, remotepassword=rserverpassword) + if pulled: + self.result[ + "transfer_status" + ] = "Received: File copied/pulled to nxos device from remote scp server." + else: + self.result["failed"] = True + + def run(self): + self.result["failed"] = False + remote_file = self._module.params["remote_file"] + local_file = self._module.params["local_file"] or remote_file.split("/")[-1] + file_system = self._module.params["file_system"] + # Note: This is the local file directory on the remote nxos device. + local_file_dir = self._module.params["local_file_directory"] + + if not self._module.check_mode: + self.copy_file_from_remote(local_file, local_file_dir, file_system) + + self.result["remote_file"] = remote_file + if local_file_dir: + dir = local_file_dir + else: + dir = "" + self.result["local_file"] = file_system + dir + "/" + local_file + self.result["remote_scp_server"] = self._module.params["remote_scp_server"] + self.result["file_system"] = self._module.params["file_system"] + + if not self.result["failed"]: + self.result["changed"] = True + + return self.result + + +def main(): + argument_spec = dict( + vrf=dict(type="str", default="management"), + connect_ssh_port=dict(type="int", default=22), + file_system=dict(type="str", default="bootflash:"), + file_pull=dict(type="bool", default=False), + file_pull_timeout=dict(type="int", default=300), + file_pull_protocol=dict( + type="str", + default="scp", + choices=["scp", "sftp", "http", "https", "tftp", "ftp"], + ), + file_pull_compact=dict(type="bool", default=False), + file_pull_kstack=dict(type="bool", default=False), + local_file=dict(type="path"), + local_file_directory=dict(type="path"), + remote_file=dict(type="path"), + remote_scp_server=dict(type="str"), + remote_scp_server_user=dict(type="str"), + remote_scp_server_password=dict(no_log=True), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[("file_pull", True, ("remote_file", "remote_scp_server"))], + required_together=[("remote_scp_server", "remote_scp_server_user")], + supports_check_mode=True, + ) + + file_pull = module.params["file_pull"] + + warnings = list() + + if file_pull: + result = FilePull(module).run() + else: + result = FilePush(module).run() + + result["warnings"] = warnings + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py new file mode 100644 index 00000000..c473ff5a --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py @@ -0,0 +1,342 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_gir +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Trigger a graceful removal or insertion (GIR) of the switch. +description: +- Trigger a graceful removal or insertion (GIR) of the switch. +- GIR processing may take more than 2 minutes. Timeout settings are automatically + extended to 200s when user timeout settings are insufficient. +version_added: 1.0.0 +author: +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- C(state) has effect only in combination with C(system_mode_maintenance_timeout) + or C(system_mode_maintenance_on_reload_reset_reason). +- Using C(system_mode_maintenance) and C(system_mode_maintenance_dont_generate_profile) + would make the module fail, but the system mode will be triggered anyway. +options: + system_mode_maintenance: + description: + - When C(system_mode_maintenance=true) it puts all enabled protocols in maintenance + mode (using the isolate command). When C(system_mode_maintenance=false) it puts + all enabled protocols in normal mode (using the no isolate command). + type: bool + system_mode_maintenance_dont_generate_profile: + description: + - When C(system_mode_maintenance_dont_generate_profile=true) it prevents the dynamic + searching of enabled protocols and executes commands configured in a maintenance-mode + profile. Use this option if you want the system to use a maintenance-mode profile + that you have created. When C(system_mode_maintenance_dont_generate_profile=false) + it prevents the dynamic searching of enabled protocols and executes commands + configured in a normal-mode profile. Use this option if you want the system + to use a normal-mode profile that you have created. + type: bool + system_mode_maintenance_timeout: + description: + - Keeps the switch in maintenance mode for a specified number of minutes. Range + is 5-65535. + type: str + system_mode_maintenance_shutdown: + description: + - Shuts down all protocols, vPC domains, and interfaces except the management + interface (using the shutdown command). This option is disruptive while C(system_mode_maintenance) + (which uses the isolate command) is not. + type: bool + system_mode_maintenance_on_reload_reset_reason: + description: + - Boots the switch into maintenance mode automatically in the event of a specified + system crash. Note that not all reset reasons are applicable for all platforms. + Also if reset reason is set to match_any, it is not idempotent as it turns on + all reset reasons. If reset reason is match_any and state is absent, it turns + off all the reset reasons. + choices: + - hw_error + - svc_failure + - kern_failure + - wdog_timeout + - fatal_error + - lc_failure + - match_any + - manual_reload + - any_other + - maintenance + type: str + state: + description: + - Specify desired state of the resource. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# Trigger system maintenance mode +- cisco.nxos.nxos_gir: + system_mode_maintenance: true + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +# Trigger system normal mode +- cisco.nxos.nxos_gir: + system_mode_maintenance: false + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +# Configure on-reload reset-reason for maintenance mode +- cisco.nxos.nxos_gir: + system_mode_maintenance_on_reload_reset_reason: manual_reload + state: present + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +# Add on-reload reset-reason for maintenance mode +- cisco.nxos.nxos_gir: + system_mode_maintenance_on_reload_reset_reason: hw_error + state: present + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +# Remove on-reload reset-reason for maintenance mode +- cisco.nxos.nxos_gir: + system_mode_maintenance_on_reload_reset_reason: manual_reload + state: absent + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +# Set timeout for maintenance mode +- cisco.nxos.nxos_gir: + system_mode_maintenance_timeout: 30 + state: present + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +# Remove timeout for maintenance mode +- cisco.nxos.nxos_gir: + system_mode_maintenance_timeout: 30 + state: absent + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +""" + +RETURN = """ +final_system_mode: + description: describe the last system mode + returned: verbose mode + type: str + sample: normal +updates: + description: commands sent to the device + returned: verbose mode + type: list + sample: ["terminal dont-ask", "system mode maintenance timeout 10"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def get_system_mode(module): + command = {"command": "show system mode", "output": "text"} + body = run_commands(module, [command])[0] + if body and "normal" in body.lower(): + mode = "normal" + else: + mode = "maintenance" + return mode + + +def get_maintenance_timeout(module): + command = {"command": "show maintenance timeout", "output": "text"} + body = run_commands(module, [command])[0] + timeout = body.split()[4] + return timeout + + +def get_reset_reasons(module): + command = { + "command": "show maintenance on-reload reset-reasons", + "output": "text", + } + body = run_commands(module, [command])[0] + return body + + +def get_commands(module, state, mode): + commands = list() + if module.params["system_mode_maintenance"] is True and mode == "normal": + commands.append("system mode maintenance") + elif module.params["system_mode_maintenance"] is False and mode == "maintenance": + commands.append("no system mode maintenance") + + elif ( + module.params["system_mode_maintenance_dont_generate_profile"] is True and mode == "normal" + ): + commands.append("system mode maintenance dont-generate-profile") + elif ( + module.params["system_mode_maintenance_dont_generate_profile"] is False + and mode == "maintenance" + ): + commands.append("no system mode maintenance dont-generate-profile") + + elif module.params["system_mode_maintenance_timeout"]: + timeout = get_maintenance_timeout(module) + if state == "present" and timeout != module.params["system_mode_maintenance_timeout"]: + commands.append( + "system mode maintenance timeout {0}".format( + module.params["system_mode_maintenance_timeout"], + ), + ) + elif state == "absent" and timeout == module.params["system_mode_maintenance_timeout"]: + commands.append( + "no system mode maintenance timeout {0}".format( + module.params["system_mode_maintenance_timeout"], + ), + ) + + elif module.params["system_mode_maintenance_shutdown"] and mode == "normal": + commands.append("system mode maintenance shutdown") + elif module.params["system_mode_maintenance_shutdown"] is False and mode == "maintenance": + commands.append("no system mode maintenance") + + elif module.params["system_mode_maintenance_on_reload_reset_reason"]: + reset_reasons = get_reset_reasons(module) + if ( + state == "present" + and module.params["system_mode_maintenance_on_reload_reset_reason"].lower() + not in reset_reasons.lower() + ): + commands.append( + "system mode maintenance on-reload " + "reset-reason {0}".format( + module.params["system_mode_maintenance_on_reload_reset_reason"], + ), + ) + elif ( + state == "absent" + and module.params["system_mode_maintenance_on_reload_reset_reason"].lower() + in reset_reasons.lower() + ): + commands.append( + "no system mode maintenance on-reload " + "reset-reason {0}".format( + module.params["system_mode_maintenance_on_reload_reset_reason"], + ), + ) + + if commands: + commands.insert(0, "terminal dont-ask") + return commands + + +def main(): + argument_spec = dict( + system_mode_maintenance=dict(required=False, type="bool"), + system_mode_maintenance_dont_generate_profile=dict(required=False, type="bool"), + system_mode_maintenance_timeout=dict(required=False, type="str"), + system_mode_maintenance_shutdown=dict(required=False, type="bool"), + system_mode_maintenance_on_reload_reset_reason=dict( + required=False, + choices=[ + "hw_error", + "svc_failure", + "kern_failure", + "wdog_timeout", + "fatal_error", + "lc_failure", + "match_any", + "manual_reload", + "any_other", + "maintenance", + ], + ), + state=dict(choices=["absent", "present"], default="present", required=False), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[ + [ + "system_mode_maintenance", + "system_mode_maintenance_dont_generate_profile", + "system_mode_maintenance_timeout", + "system_mode_maintenance_shutdown", + "system_mode_maintenance_on_reload_reset_reason", + ], + ], + required_one_of=[ + [ + "system_mode_maintenance", + "system_mode_maintenance_dont_generate_profile", + "system_mode_maintenance_timeout", + "system_mode_maintenance_shutdown", + "system_mode_maintenance_on_reload_reset_reason", + ], + ], + supports_check_mode=True, + ) + + warnings = list() + + state = module.params["state"] + mode = get_system_mode(module) + commands = get_commands(module, state, mode) + changed = False + if commands: + if module.check_mode: + module.exit_json(changed=True, commands=commands) + else: + load_config(module, commands) + changed = True + + result = {} + result["changed"] = changed + if module._verbosity > 0: + final_system_mode = get_system_mode(module) + result["final_system_mode"] = final_system_mode + result["updates"] = commands + + result["warnings"] = warnings + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py new file mode 100644 index 00000000..84cfc145 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py @@ -0,0 +1,214 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_gir_profile_management +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Create a maintenance-mode or normal-mode profile for GIR. +description: +- Manage a maintenance-mode or normal-mode profile with configuration commands that + can be applied during graceful removal or graceful insertion. +version_added: 1.0.0 +author: +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- C(state=absent) removes the whole profile. +options: + commands: + description: + - List of commands to be included into the profile. + type: list + elements: str + mode: + description: + - Configure the profile as Maintenance or Normal mode. + required: true + choices: + - maintenance + - normal + type: str + state: + description: + - Specify desired state of the resource. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# Create a maintenance-mode profile +- cisco.nxos.nxos_gir_profile_management: + mode: maintenance + commands: + - router eigrp 11 + - isolate + +# Remove the maintenance-mode profile +- cisco.nxos.nxos_gir_profile_management: + mode: maintenance + state: absent +""" + +RETURN = """ +proposed: + description: list of commands passed into module. + returned: verbose mode + type: list + sample: ["router eigrp 11", "isolate"] +existing: + description: list of existing profile commands. + returned: verbose mode + type: list + sample: ["router bgp 65535","isolate","router eigrp 10","isolate", + "diagnostic bootup level complete"] +end_state: + description: list of profile entries after module execution. + returned: verbose mode + type: list + sample: ["router bgp 65535","isolate","router eigrp 10","isolate", + "diagnostic bootup level complete","router eigrp 11", "isolate"] +updates: + description: commands sent to the device + returned: always + type: list + sample: ["configure maintenance profile maintenance-mode", + "router eigrp 11","isolate"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +def get_existing(module): + existing = [] + netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) + + if module.params["mode"] == "maintenance": + parents = ["configure maintenance profile maintenance-mode"] + else: + parents = ["configure maintenance profile normal-mode"] + + config = netcfg.get_section(parents) + if config: + existing = config.splitlines() + existing = [cmd.strip() for cmd in existing] + existing.pop(0) + + return existing + + +def state_present(module, existing, commands): + cmds = list() + if existing == commands: + # Idempotent case + return cmds + cmds.extend(commands) + if module.params["mode"] == "maintenance": + cmds.insert(0, "configure maintenance profile maintenance-mode") + else: + cmds.insert(0, "configure maintenance profile normal-mode") + + return cmds + + +def state_absent(module, existing, commands): + if module.params["mode"] == "maintenance": + cmds = ["no configure maintenance profile maintenance-mode"] + else: + cmds = ["no configure maintenance profile normal-mode"] + return cmds + + +def invoke(name, *args, **kwargs): + func = globals().get(name) + if func: + return func(*args, **kwargs) + + +def main(): + argument_spec = dict( + commands=dict(required=False, type="list", elements="str"), + mode=dict(required=True, choices=["maintenance", "normal"]), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + state = module.params["state"] + commands = module.params["commands"] or [] + + if state == "absent" and commands: + module.fail_json(msg="when state is absent, no command can be used.") + + existing = invoke("get_existing", module) + end_state = existing + changed = False + + result = {} + cmds = [] + if state == "present" or (state == "absent" and existing): + cmds = invoke("state_%s" % state, module, existing, commands) + + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + if cmds: + load_config(module, cmds) + changed = True + end_state = invoke("get_existing", module) + + result["changed"] = changed + if module._verbosity > 0: + end_state = invoke("get_existing", module) + result["end_state"] = end_state + result["existing"] = existing + result["proposed"] = commands + result["updates"] = cmds + + result["warnings"] = warnings + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py new file mode 100644 index 00000000..42e45677 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py @@ -0,0 +1,229 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for nxos_hostname +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_hostname +short_description: Hostname resource module. +description: +- This module manages hostname configuration on devices running Cisco NX-OS. +version_added: 2.9.0 +notes: +- Tested against NX-OS 9.3.6. +- This module works with connection C(network_cli) and C(httpapi). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section hostname). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dictionary of hostname configuration. + type: dict + suboptions: + hostname: + description: Hostname of the device. + type: str + state: + description: + - The state the configuration should be left in. + - The states I(merged), I(replaced) and I(overridden) have identical + behaviour for this module. + - Refer to examples for more details. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - parsed + - gathered + - rendered + default: merged +""" + +EXAMPLES = """ +# Using merged (replaced, overridden has the same behaviour) + +# Before state: +# ------------- +# nxos-9k-rdo# show running-config | section ^hostname +# nxos-9k-rdo# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_hostname: + config: + hostname: NXOSv-9k + +# Task output +# ------------- +# before: {} +# +# commands: +# - hostname NXOSv-9k +# +# after: +# hostname: NXOSv-9k + +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section ^hostname +# hostname NXOSv-9k +# + +# Using deleted + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section ^hostname +# hostname NXOSv-9k + +- name: Delete hostname from running-config + cisco.nxos.nxos_hostname: + state: deleted + +# Task output +# ------------- +# before: +# hostname: NXOSv-9k +# +# commands: +# - no hostname NXOSv-9k +# +# after: {} + +# Using gathered + +- name: Gather hostname facts using gathered + cisco.nxos.nxos_hostname: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# hostname: NXOSv-9k + +# Using rendered + +- name: Render platform specific configuration lines (without connecting to the device) + cisco.nxos.nxos_hostname: + config: + hostname: NXOSv-9k + +# Task Output (redacted) +# ----------------------- +# rendered: +# - hostname NXOSv-9k + +# Using parsed + +# parsed.cfg +# ------------ +# hostname NXOSv-9k + +- name: Parse externally provided hostname config + cisco.nxos.nxos_hostname: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# hostname: NXOSv-9k + +""" + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: dict + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: list + sample: + - hostname switch01 +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - hostname switch01 +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: list + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: list + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hostname.hostname import ( + HostnameArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.hostname.hostname import ( + Hostname, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=HostnameArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Hostname(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py new file mode 100644 index 00000000..ee59b7dd --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py @@ -0,0 +1,506 @@ +#!/usr/bin/python +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = """ +module: nxos_hsrp +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages HSRP configuration on NX-OS switches. +description: +- Manages HSRP configuration on NX-OS switches. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- HSRP feature needs to be enabled first on the system. +- SVIs must exist before using this module. +- Interface must be a L3 port before using this module. +- HSRP cannot be configured on loopback interfaces. +- MD5 authentication is only possible with HSRPv2 while it is ignored if HSRPv1 is + used instead, while it will not raise any error. Here we allow MD5 authentication + only with HSRPv2 in order to enforce better practice. +options: + group: + description: + - HSRP group number. + required: true + type: str + interface: + description: + - Full name of interface that is being managed for HSRP. + required: true + type: str + version: + description: + - HSRP version. + default: '1' + choices: + - '1' + - '2' + type: str + priority: + description: + - HSRP priority or keyword 'default'. + type: str + preempt: + description: + - Enable/Disable preempt. + choices: + - enabled + - disabled + type: str + vip: + description: + - HSRP virtual IP address or keyword 'default' + type: str + auth_string: + description: + - Authentication string. If this needs to be hidden(for md5 type), the string + should be 7 followed by the key string. Otherwise, it can be 0 followed by key + string or just key string (for backward compatibility). For text type, this + should be just be a key string. if this is 'default', authentication is removed. + type: str + auth_type: + description: + - Authentication type. + choices: + - text + - md5 + type: str + state: + description: + - Specify desired state of the resource. + choices: + - present + - absent + default: present + type: str +""" + +EXAMPLES = """ +- name: Ensure HSRP is configured with following params on a SVI + cisco.nxos.nxos_hsrp: + group: 10 + vip: 10.1.1.1 + priority: 150 + interface: vlan10 + preempt: enabled + +- name: Ensure HSRP is configured with following params on a SVI with clear text authentication + cisco.nxos.nxos_hsrp: + group: 10 + vip: 10.1.1.1 + priority: 150 + interface: vlan10 + preempt: enabled + auth_type: text + auth_string: CISCO + +- name: Ensure HSRP is configured with md5 authentication and clear authentication + string + cisco.nxos.nxos_hsrp: + group: 10 + vip: 10.1.1.1 + priority: 150 + interface: vlan10 + preempt: enabled + auth_type: md5 + auth_string: 0 1234 + +- name: Ensure HSRP is configured with md5 authentication and hidden authentication + string + cisco.nxos.nxos_hsrp: + group: 10 + vip: 10.1.1.1 + priority: 150 + interface: vlan10 + preempt: enabled + auth_type: md5 + auth_string: 7 1234 + +- name: Remove HSRP config for given interface, group, and VIP + cisco.nxos.nxos_hsrp: + group: 10 + interface: vlan10 + vip: 10.1.1.1 + state: absent +""" + +RETURN = r""" +commands: + description: commands sent to the device + returned: always + type: list + sample: ["interface vlan10", "hsrp version 2", "hsrp 30", "ip 10.30.1.1"] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + get_interface_type, + load_config, + run_commands, +) + + +PARAM_TO_DEFAULT_KEYMAP = { + "vip": None, + "priority": "100", + "auth_type": "text", + "auth_string": "cisco", +} + + +def apply_key_map(key_map, table): + new_dict = {} + for key in table: + new_key = key_map.get(key) + if new_key: + value = table.get(key) + if value: + new_dict[new_key] = str(value) + else: + new_dict[new_key] = value + return new_dict + + +def get_interface_mode(interface, intf_type, module): + command = "show interface {0} | json".format(interface) + interface = {} + mode = "unknown" + try: + body = run_commands(module, [command])[0] + except IndexError: + return None + + if intf_type in ["ethernet", "portchannel"]: + interface_table = body["TABLE_interface"]["ROW_interface"] + mode = str(interface_table.get("eth_mode", "layer3")) + if mode == "access" or mode == "trunk": + mode = "layer2" + elif intf_type == "svi": + mode = "layer3" + return mode + + +def get_hsrp_group(group, interface, module): + command = "show hsrp group {0} all | json".format(group) + hsrp = {} + + hsrp_key = { + "sh_if_index": "interface", + "sh_group_num": "group", + "sh_group_version": "version", + "sh_cfg_prio": "priority", + "sh_preempt": "preempt", + "sh_vip": "vip", + "sh_authentication_type": "auth_type", + "sh_keystring_attr": "auth_enc", + "sh_authentication_data": "auth_string", + } + + try: + body = run_commands(module, [command])[0] + hsrp_table = body["TABLE_grp_detail"]["ROW_grp_detail"] + if "sh_keystring_attr" not in hsrp_table: + del hsrp_key["sh_keystring_attr"] + if "unknown enum:" in str(hsrp_table): + hsrp_table = get_hsrp_group_unknown_enum(module, command, hsrp_table) + except (AttributeError, IndexError, TypeError, KeyError): + return {} + + if isinstance(hsrp_table, dict): + hsrp_table = [hsrp_table] + + for hsrp_group in hsrp_table: + parsed_hsrp = apply_key_map(hsrp_key, hsrp_group) + + parsed_hsrp["interface"] = parsed_hsrp["interface"].lower() + + if parsed_hsrp["version"] == "v1": + parsed_hsrp["version"] = "1" + elif parsed_hsrp["version"] == "v2": + parsed_hsrp["version"] = "2" + + if parsed_hsrp["auth_type"] == "md5": + if parsed_hsrp["auth_enc"] == "hidden": + parsed_hsrp["auth_enc"] = "7" + else: + parsed_hsrp["auth_enc"] = "0" + + if parsed_hsrp["interface"] == interface: + return parsed_hsrp + + return hsrp + + +def get_hsrp_group_unknown_enum(module, command, hsrp_table): + """Some older NXOS images fail to set the attr values when using structured output and + instead set the values to <unknown enum>. This fallback method is a workaround that + uses an unstructured (text) request to query the device a second time. + 'sh_preempt' is currently the only attr affected. Add checks for other attrs as needed. + """ + if "unknown enum:" in hsrp_table["sh_preempt"]: + cmd = {"output": "text", "command": command.split("|")[0]} + out = run_commands(module, cmd)[0] + hsrp_table["sh_preempt"] = "enabled" if ("may preempt" in out) else "disabled" + return hsrp_table + + +def get_commands_remove_hsrp(group, interface): + commands = ["interface {0}".format(interface), "no hsrp {0}".format(group)] + return commands + + +def get_commands_config_hsrp(delta, interface, args, existing): + commands = [] + + config_args = { + "group": "hsrp {group}", + "priority": "{priority}", + "preempt": "{preempt}", + "vip": "{vip}", + } + + preempt = delta.get("preempt", None) + group = delta.get("group", None) + vip = delta.get("vip", None) + priority = delta.get("priority", None) + + if preempt: + if preempt == "enabled": + delta["preempt"] = "preempt" + elif preempt == "disabled": + delta["preempt"] = "no preempt" + + if priority: + if priority == "default": + if existing and existing.get("priority") != PARAM_TO_DEFAULT_KEYMAP.get("priority"): + delta["priority"] = "no priority" + else: + del delta["priority"] + else: + delta["priority"] = "priority {0}".format(delta["priority"]) + + if vip: + if vip == "default": + if existing and existing.get("vip") != PARAM_TO_DEFAULT_KEYMAP.get("vip"): + delta["vip"] = "no ip" + else: + del delta["vip"] + else: + delta["vip"] = "ip {0}".format(delta["vip"]) + + for key in delta: + command = config_args.get(key, "DNE").format(**delta) + if command and command != "DNE": + if key == "group": + commands.insert(0, command) + else: + commands.append(command) + command = None + + auth_type = delta.get("auth_type", None) + auth_string = delta.get("auth_string", None) + auth_enc = delta.get("auth_enc", None) + if auth_type or auth_string: + if not auth_type: + auth_type = args["auth_type"] + elif not auth_string: + auth_string = args["auth_string"] + if auth_string != "default": + if auth_type == "md5": + command = "authentication md5 key-string {0} {1}".format(auth_enc, auth_string) + commands.append(command) + elif auth_type == "text": + command = "authentication text {0}".format(auth_string) + commands.append(command) + else: + if existing and existing.get("auth_string") != PARAM_TO_DEFAULT_KEYMAP.get( + "auth_string", + ): + commands.append("no authentication") + + if commands and not group: + commands.insert(0, "hsrp {0}".format(args["group"])) + + version = delta.get("version", None) + if version: + if version == "2": + command = "hsrp version 2" + elif version == "1": + command = "hsrp version 1" + commands.insert(0, command) + commands.insert(0, "interface {0}".format(interface)) + + if commands: + if not commands[0].startswith("interface"): + commands.insert(0, "interface {0}".format(interface)) + + return commands + + +def is_default(interface, module): + command = "show run interface {0}".format(interface) + + try: + body = run_commands(module, [command], check_rc=False)[0] + if "invalid" in body.lower(): + return "DNE" + else: + raw_list = body.split("\n") + if raw_list[-1].startswith("interface"): + return True + else: + return False + except KeyError: + return "DNE" + + +def validate_config(body, vip, module): + new_body = "".join(body) + if "invalid ip address" in new_body.lower(): + module.fail_json(msg="Invalid VIP. Possible duplicate IP address.", vip=vip) + + +def main(): + argument_spec = dict( + group=dict(required=True, type="str"), + interface=dict(required=True), + version=dict(choices=["1", "2"], default="1", required=False), + priority=dict(type="str", required=False), + preempt=dict(type="str", choices=["disabled", "enabled"], required=False), + vip=dict(type="str", required=False), + auth_type=dict(choices=["text", "md5"], required=False), + auth_string=dict(type="str", required=False), + state=dict(choices=["absent", "present"], required=False, default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = dict(changed=False, warnings=warnings) + + interface = module.params["interface"].lower() + group = module.params["group"] + version = module.params["version"] + state = module.params["state"] + priority = module.params["priority"] + preempt = module.params["preempt"] + vip = module.params["vip"] + auth_type = module.params["auth_type"] + auth_full_string = module.params["auth_string"] + auth_enc = "0" + auth_string = None + if auth_full_string: + kstr = auth_full_string.split() + if len(kstr) == 2: + auth_enc = kstr[0] + auth_string = kstr[1] + elif len(kstr) == 1: + auth_string = kstr[0] + else: + module.fail_json(msg="Invalid auth_string") + if auth_enc != "0" and auth_enc != "7": + module.fail_json(msg="Invalid auth_string, only 0 or 7 allowed") + + device_info = get_capabilities(module) + network_api = device_info.get("network_api", "nxapi") + + intf_type = get_interface_type(interface) + if intf_type != "ethernet" and network_api == "cliconf": + if is_default(interface, module) == "DNE": + module.fail_json( + msg="That interface does not exist yet. Create " "it first.", + interface=interface, + ) + if intf_type == "loopback": + module.fail_json( + msg="Loopback interfaces don't support HSRP.", + interface=interface, + ) + + mode = get_interface_mode(interface, intf_type, module) + if mode == "layer2": + module.fail_json( + msg="That interface is a layer2 port.\nMake it " "a layer 3 port first.", + interface=interface, + ) + + if auth_type or auth_string: + if not (auth_type and auth_string): + module.fail_json( + msg="When using auth parameters, you need BOTH " "auth_type AND auth_string.", + ) + + args = dict( + group=group, + version=version, + priority=priority, + preempt=preempt, + vip=vip, + auth_type=auth_type, + auth_string=auth_string, + auth_enc=auth_enc, + ) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + + existing = get_hsrp_group(group, interface, module) + + # This will enforce better practice with md5 and hsrp version. + if proposed.get("auth_type", None) == "md5": + if proposed["version"] == "1": + module.fail_json(msg="It's recommended to use HSRP v2 " "when auth_type=md5") + + elif not proposed.get("auth_type", None) and existing: + if (proposed["version"] == "1" and existing["auth_type"] == "md5") and state == "present": + module.fail_json( + msg="Existing auth_type is md5. It's recommended " "to use HSRP v2 when using md5", + ) + + commands = [] + if state == "present": + delta = dict(set(proposed.items()).difference(existing.items())) + if delta: + command = get_commands_config_hsrp(delta, interface, args, existing) + commands.extend(command) + + elif state == "absent": + if existing: + command = get_commands_remove_hsrp(group, interface) + commands.extend(command) + + if commands: + if module.check_mode: + module.exit_json(**results) + else: + load_config(module, commands) + + # validate IP + if network_api == "cliconf" and state == "present": + commands.insert(0, "config t") + body = run_commands(module, commands) + validate_config(body, vip, module) + + results["changed"] = True + + if "configure" in commands: + commands.pop(0) + + results["commands"] = commands + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py new file mode 100644 index 00000000..e5ac6737 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# 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 nxos_hsrp_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_hsrp_interfaces +short_description: HSRP interfaces resource module +description: Manages Hot Standby Router Protocol (HSRP) interface attributes. +version_added: 1.0.0 +author: Chris Van Heuveln (@chrisvanheuveln) +notes: +- Tested against NX-OS 7.0(3)I5(1). +- Feature bfd should be enabled for this module. +- Unsupported for Cisco MDS +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^interface'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: The provided configuration + type: list + elements: dict + suboptions: + name: + type: str + description: The name of the interface. + bfd: + type: str + description: + - Enable/Disable HSRP Bidirectional Forwarding Detection (BFD) on the interface. + choices: + - enable + - disable + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using deleted + +- name: Configure hsrp attributes on interfaces + cisco.nxos.nxos_hsrp_interfaces: + config: + - name: Ethernet1/1 + - name: Ethernet1/2 + operation: deleted + + +# Using merged + +- name: Configure hsrp attributes on interfaces + cisco.nxos.nxos_hsrp_interfaces: + config: + - name: Ethernet1/1 + bfd: enable + - name: Ethernet1/2 + bfd: disable + operation: merged + + +# Using overridden + +- name: Configure hsrp attributes on interfaces + cisco.nxos.nxos_hsrp_interfaces: + config: + - name: Ethernet1/1 + bfd: enable + - name: Ethernet1/2 + bfd: disable + operation: overridden + + +# Using replaced + +- name: Configure hsrp attributes on interfaces + cisco.nxos.nxos_hsrp_interfaces: + config: + - name: Ethernet1/1 + bfd: enable + - name: Ethernet1/2 + bfd: disable + operation: replaced + +# Using rendered + +- name: Use rendered state to convert task input to device specific commands + cisco.nxos.nxos_hsrp_interfaces: + config: + - name: Ethernet1/800 + bfd: enable + - name: Ethernet1/801 + bfd: enable + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "interface Ethernet1/800" +# - "hsrp bfd" +# - "interface Ethernet1/801" +# - "hsrp bfd" + +# Using parsed + +# parsed.cfg +# ------------ +# interface Ethernet1/800 +# no switchport +# hsrp bfd +# interface Ethernet1/801 +# no switchport +# hsrp bfd + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_hsrp_interfaces: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- + +# parsed: +# - name: Ethernet1/800 +# bfd: enable +# - name: Ethernet1/801 +# bfd: enable + +# Using gathered + +# Existing device config state +# ------------------------------- + +# interface Ethernet1/1 +# no switchport +# hsrp bfd +# interface Ethernet1/2 +# no switchport +# hsrp bfd +# interface Ethernet1/3 +# no switchport + +- name: Gather hsrp_interfaces facts from the device using nxos_hsrp_interfaces + cisco.nxos.nxos_hsrp_interfaces: + state: gathered + +# Task output (redacted) +# ----------------------- + +# gathered: +# - name: Ethernet1/1 +# bfd: enable +# - name: Ethernet1/2 +# bfd: enable + +""" +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: ['interface Ethernet1/1', 'hsrp bfd'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hsrp_interfaces.hsrp_interfaces import ( + Hsrp_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.hsrp_interfaces.hsrp_interfaces import ( + Hsrp_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=Hsrp_interfacesArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = Hsrp_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py new file mode 100644 index 00000000..93770616 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_igmp +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages IGMP global configuration. +description: +- Manages IGMP global configuration configuration settings. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- When C(state=default), all supported params will be reset to a default state. +- If restart is set to true with other params set, the restart will happen last, i.e. + after the configuration takes place. +options: + flush_routes: + description: + - Removes routes when the IGMP process is restarted. By default, routes are not + flushed. + type: bool + enforce_rtr_alert: + description: + - Enables or disables the enforce router alert option check for IGMPv2 and IGMPv3 + packets. + type: bool + restart: + description: + - Restarts the igmp process (using an exec config command). + type: bool + default: False + state: + description: + - Manages desired state of the resource. + default: present + choices: + - present + - default + type: str +""" +EXAMPLES = """ +- name: Default igmp global params (all params except restart) + cisco.nxos.nxos_igmp: + state: default + +- name: Ensure the following igmp global config exists on the device + cisco.nxos.nxos_igmp: + flush_routes: true + enforce_rtr_alert: true + +- name: Restart the igmp process + cisco.nxos.nxos_igmp: + restart: true +""" + +RETURN = """ +updates: + description: commands sent to the device + returned: always + type: list + sample: ["ip igmp flush-routes"] +""" +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def get_current(module): + output = run_commands(module, {"command": "show running-config", "output": "text"}) + return { + "flush_routes": "ip igmp flush-routes" in output[0], + "enforce_rtr_alert": "ip igmp enforce-router-alert" in output[0], + } + + +def get_desired(module): + return { + "flush_routes": module.params["flush_routes"], + "enforce_rtr_alert": module.params["enforce_rtr_alert"], + } + + +def main(): + argument_spec = dict( + flush_routes=dict(type="bool"), + enforce_rtr_alert=dict(type="bool"), + restart=dict(type="bool", default=False), + state=dict(choices=["present", "default"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + current = get_current(module) + desired = get_desired(module) + + state = module.params["state"] + + commands = list() + + if state == "default": + if current["flush_routes"]: + commands.append("no ip igmp flush-routes") + if current["enforce_rtr_alert"]: + commands.append("no ip igmp enforce-router-alert") + + elif state == "present": + ldict = { + "flush_routes": "flush-routes", + "enforce_rtr_alert": "enforce-router-alert", + } + for arg in ["flush_routes", "enforce_rtr_alert"]: + if desired[arg] and not current[arg]: + commands.append("ip igmp {0}".format(ldict.get(arg))) + elif current[arg] and not desired[arg]: + commands.append("no ip igmp {0}".format(ldict.get(arg))) + + result = {"changed": False, "updates": commands, "warnings": warnings} + + if commands: + if not module.check_mode: + load_config(module, commands) + result["changed"] = True + + if module.params["restart"]: + cmd = {"command": "restart igmp", "output": "text"} + run_commands(module, cmd) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py new file mode 100644 index 00000000..105dac5e --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py @@ -0,0 +1,650 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_igmp_interface +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages IGMP interface configuration. +description: +- Manages IGMP interface configuration settings. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- When C(state=default), supported params will be reset to a default state. These + include C(version), C(startup_query_interval), C(startup_query_count), C(robustness), + C(querier_timeout), C(query_mrt), C(query_interval), C(last_member_qrt), C(last_member_query_count), + C(group_timeout), C(report_llg), and C(immediate_leave). +- When C(state=absent), all configs for C(oif_ps), and C(oif_routemap) will be removed. +- PIM must be enabled to use this module. +- This module is for Layer 3 interfaces. +- Route-map check not performed (same as CLI) check when configuring route-map with + 'static-oif' +- If restart is set to true with other params set, the restart will happen last, i.e. + after the configuration takes place. However, 'restart' itself is not idempotent + as it is an action and not configuration. +options: + interface: + description: + - The full interface name for IGMP configuration. e.g. I(Ethernet1/2). + required: true + type: str + version: + description: + - IGMP version. It can be 2 or 3 or keyword 'default'. + choices: + - '2' + - '3' + - default + type: str + startup_query_interval: + description: + - Query interval used when the IGMP process starts up. The range is from 1 to + 18000 or keyword 'default'. The default is 31. + type: str + startup_query_count: + description: + - Query count used when the IGMP process starts up. The range is from 1 to 10 + or keyword 'default'. The default is 2. + type: str + robustness: + description: + - Sets the robustness variable. Values can range from 1 to 7 or keyword 'default'. + The default is 2. + type: str + querier_timeout: + description: + - Sets the querier timeout that the software uses when deciding to take over as + the querier. Values can range from 1 to 65535 seconds or keyword 'default'. + The default is 255 seconds. + type: str + query_mrt: + description: + - Sets the response time advertised in IGMP queries. Values can range from 1 to + 25 seconds or keyword 'default'. The default is 10 seconds. + type: str + query_interval: + description: + - Sets the frequency at which the software sends IGMP host query messages. Values + can range from 1 to 18000 seconds or keyword 'default'. The default is 125 seconds. + type: str + last_member_qrt: + description: + - Sets the query interval waited after sending membership reports before the software + deletes the group state. Values can range from 1 to 25 seconds or keyword 'default'. + The default is 1 second. + type: str + last_member_query_count: + description: + - Sets the number of times that the software sends an IGMP query in response to + a host leave message. Values can range from 1 to 5 or keyword 'default'. The + default is 2. + type: str + group_timeout: + description: + - Sets the group membership timeout for IGMPv2. Values can range from 3 to 65,535 + seconds or keyword 'default'. The default is 260 seconds. + type: str + report_llg: + description: + - Configures report-link-local-groups. Enables sending reports for groups in 224.0.0.0/24. + Reports are always sent for nonlink local groups. By default, reports are not + sent for link local groups. + type: bool + immediate_leave: + description: + - Enables the device to remove the group entry from the multicast routing table + immediately upon receiving a leave message for the group. Use this command to + minimize the leave latency of IGMPv2 group memberships on a given IGMP interface + because the device does not send group-specific queries. The default is disabled. + type: bool + oif_routemap: + description: + - Configure a routemap for static outgoing interface (OIF) or keyword 'default'. + type: str + oif_ps: + description: + - Configure prefixes and sources for static outgoing interface (OIF). This is + a list of dict where each dict has source and prefix defined or just prefix + if source is not needed. The specified values will be configured on the device + and if any previous prefix/sources exist, they will be removed. Keyword 'default' + is also accepted which removes all existing prefix/sources. + type: raw + restart: + description: + - Restart IGMP. This is NOT idempotent as this is action only. + type: bool + default: false + state: + description: + - Manages desired state of the resource. + default: present + choices: + - present + - absent + - default + type: str +""" +EXAMPLES = """ +- cisco.nxos.nxos_igmp_interface: + interface: ethernet1/32 + startup_query_interval: 30 + oif_ps: + - {prefix: 238.2.2.6} + - {source: 192.168.0.1, prefix: 238.2.2.5} + state: present +""" +RETURN = """ +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"startup_query_count": "30", + "oif_ps": [{'prefix': '238.2.2.6'}, {'source': '192.168.0.1', 'prefix': '238.2.2.5'}]} +existing: + description: k/v pairs of existing igmp_interface configuration + returned: always + type: dict + sample: {"startup_query_count": "2", "oif_ps": []} +end_state: + description: k/v pairs of igmp interface configuration after module execution + returned: always + type: dict + sample: {"startup_query_count": "30", + "oif_ps": [{'prefix': '238.2.2.6'}, {'source': '192.168.0.1', 'prefix': '238.2.2.5'}]} +updates: + description: commands sent to the device + returned: always + type: list + sample: ["interface Ethernet1/32", "ip igmp startup-query-count 30", + "ip igmp static-oif 238.2.2.6", "ip igmp static-oif 238.2.2.5 source 192.168.0.1"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_interface_type, + load_config, + run_commands, +) + + +def execute_show_command(command, module, command_type="cli_show"): + if command_type == "cli_show_ascii": + cmds = [{"command": command, "output": "text"}] + else: + cmds = [{"command": command, "output": "json"}] + + return run_commands(module, cmds) + + +def get_interface_mode(interface, intf_type, module): + command = "show interface {0}".format(interface) + interface = {} + mode = "unknown" + + if intf_type in ["ethernet", "portchannel"]: + body = execute_show_command(command, module)[0] + interface_table = body["TABLE_interface"]["ROW_interface"] + mode = str(interface_table.get("eth_mode", "layer3")) + if mode == "access" or mode == "trunk": + mode = "layer2" + elif intf_type == "loopback" or intf_type == "svi": + mode = "layer3" + return mode + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if new_key: + value = table.get(key) + if value: + new_dict[new_key] = value + else: + new_dict[new_key] = value + return new_dict + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_igmp_interface(module, interface): + command = "show ip igmp interface {0}".format(interface) + igmp = {} + + key_map = { + "IGMPVersion": "version", + "ConfiguredStartupQueryInterval": "startup_query_interval", + "StartupQueryCount": "startup_query_count", + "RobustnessVariable": "robustness", + "ConfiguredQuerierTimeout": "querier_timeout", + "ConfiguredMaxResponseTime": "query_mrt", + "ConfiguredQueryInterval": "query_interval", + "LastMemberMTR": "last_member_qrt", + "LastMemberQueryCount": "last_member_query_count", + "ConfiguredGroupTimeout": "group_timeout", + } + + body = execute_show_command(command, module)[0] + + if body: + if "not running" in body: + return igmp + resource = body["TABLE_vrf"]["ROW_vrf"]["TABLE_if"]["ROW_if"] + igmp = apply_key_map(key_map, resource) + report_llg = str(resource["ReportingForLinkLocal"]).lower() + if report_llg == "true": + igmp["report_llg"] = True + elif report_llg == "false": + igmp["report_llg"] = False + + immediate_leave = str(resource["ImmediateLeave"]).lower() # returns en or dis + if re.search(r"^en|^true|^enabled", immediate_leave): + igmp["immediate_leave"] = True + elif re.search(r"^dis|^false|^disabled", immediate_leave): + igmp["immediate_leave"] = False + + # the next block of code is used to retrieve anything with: + # ip igmp static-oif *** i.e.. could be route-map ROUTEMAP + # or PREFIX source <ip>, etc. + command = "show run interface {0} | inc oif".format(interface) + + body = execute_show_command(command, module, command_type="cli_show_ascii")[0] + + staticoif = [] + if body: + split_body = body.split("\n") + route_map_regex = r".*ip igmp static-oif route-map\s+(?P<route_map>\S+).*" + prefix_source_regex = ( + r".*ip igmp static-oif\s+(?P<prefix>" + r"((\d+.){3}\d+))(\ssource\s" + r"(?P<source>\S+))?.*" + ) + + for line in split_body: + temp = {} + try: + match_route_map = re.match(route_map_regex, line, re.DOTALL) + route_map = match_route_map.groupdict()["route_map"] + except AttributeError: + route_map = "" + + try: + match_prefix_source = re.match(prefix_source_regex, line, re.DOTALL) + prefix_source_group = match_prefix_source.groupdict() + prefix = prefix_source_group["prefix"] + source = prefix_source_group["source"] + except AttributeError: + prefix = "" + source = "" + + if route_map: + temp["route_map"] = route_map + if prefix: + temp["prefix"] = prefix + if source: + temp["source"] = source + if temp: + staticoif.append(temp) + + igmp["oif_routemap"] = None + igmp["oif_prefix_source"] = [] + + if staticoif: + if len(staticoif) == 1 and staticoif[0].get("route_map"): + igmp["oif_routemap"] = staticoif[0]["route_map"] + else: + igmp["oif_prefix_source"] = staticoif + + return igmp + + +def config_igmp_interface(delta, existing, existing_oif_prefix_source): + CMDS = { + "version": "ip igmp version {0}", + "startup_query_interval": "ip igmp startup-query-interval {0}", + "startup_query_count": "ip igmp startup-query-count {0}", + "robustness": "ip igmp robustness-variable {0}", + "querier_timeout": "ip igmp querier-timeout {0}", + "query_mrt": "ip igmp query-max-response-time {0}", + "query_interval": "ip igmp query-interval {0}", + "last_member_qrt": "ip igmp last-member-query-response-time {0}", + "last_member_query_count": "ip igmp last-member-query-count {0}", + "group_timeout": "ip igmp group-timeout {0}", + "report_llg": "ip igmp report-link-local-groups", + "immediate_leave": "ip igmp immediate-leave", + "oif_prefix_source": "ip igmp static-oif {0} source {1} ", + "oif_routemap": "ip igmp static-oif route-map {0}", + "oif_prefix": "ip igmp static-oif {0}", + } + + commands = [] + command = None + def_vals = get_igmp_interface_defaults() + + for key, value in delta.items(): + if key == "oif_ps" and value != "default": + for each in value: + if each in existing_oif_prefix_source: + existing_oif_prefix_source.remove(each) + else: + # add new prefix/sources + pf = each["prefix"] + src = "" + if "source" in each.keys(): + src = each["source"] + if src: + commands.append(CMDS.get("oif_prefix_source").format(pf, src)) + else: + commands.append(CMDS.get("oif_prefix").format(pf)) + if existing_oif_prefix_source: + for each in existing_oif_prefix_source: + # remove stale prefix/sources + pf = each["prefix"] + src = "" + if "source" in each.keys(): + src = each["source"] + if src: + commands.append("no " + CMDS.get("oif_prefix_source").format(pf, src)) + else: + commands.append("no " + CMDS.get("oif_prefix").format(pf)) + elif key == "oif_routemap": + if value == "default": + if existing.get(key): + command = "no " + CMDS.get(key).format(existing.get(key)) + else: + command = CMDS.get(key).format(value) + elif value: + if value == "default": + if def_vals.get(key) != existing.get(key): + command = CMDS.get(key).format(def_vals.get(key)) + else: + command = CMDS.get(key).format(value) + elif not value: + command = "no {0}".format(CMDS.get(key).format(value)) + + if command: + if command not in commands: + commands.append(command) + command = None + + return commands + + +def get_igmp_interface_defaults(): + version = "2" + startup_query_interval = "31" + startup_query_count = "2" + robustness = "2" + querier_timeout = "255" + query_mrt = "10" + query_interval = "125" + last_member_qrt = "1" + last_member_query_count = "2" + group_timeout = "260" + report_llg = False + immediate_leave = False + + args = dict( + version=version, + startup_query_interval=startup_query_interval, + startup_query_count=startup_query_count, + robustness=robustness, + querier_timeout=querier_timeout, + query_mrt=query_mrt, + query_interval=query_interval, + last_member_qrt=last_member_qrt, + last_member_query_count=last_member_query_count, + group_timeout=group_timeout, + report_llg=report_llg, + immediate_leave=immediate_leave, + ) + + default = dict((param, value) for (param, value) in args.items() if value is not None) + + return default + + +def config_default_igmp_interface(existing, delta): + commands = [] + proposed = get_igmp_interface_defaults() + delta = dict(set(proposed.items()).difference(existing.items())) + if delta: + command = config_igmp_interface(delta, existing, existing_oif_prefix_source=None) + + if command: + for each in command: + commands.append(each) + + return commands + + +def config_remove_oif(existing, existing_oif_prefix_source): + commands = [] + command = None + if existing.get("oif_routemap"): + commands.append("no ip igmp static-oif route-map {0}".format(existing.get("oif_routemap"))) + elif existing_oif_prefix_source: + for each in existing_oif_prefix_source: + if each.get("prefix") and each.get("source"): + command = "no ip igmp static-oif {0} source {1} ".format( + each.get("prefix"), + each.get("source"), + ) + elif each.get("prefix"): + command = "no ip igmp static-oif {0}".format(each.get("prefix")) + if command: + commands.append(command) + command = None + + return commands + + +def main(): + argument_spec = dict( + interface=dict(required=True, type="str"), + version=dict(required=False, type="str", choices=["2", "3", "default"]), + startup_query_interval=dict(required=False, type="str"), + startup_query_count=dict(required=False, type="str"), + robustness=dict(required=False, type="str"), + querier_timeout=dict(required=False, type="str"), + query_mrt=dict(required=False, type="str"), + query_interval=dict(required=False, type="str"), + last_member_qrt=dict(required=False, type="str"), + last_member_query_count=dict(required=False, type="str"), + group_timeout=dict(required=False, type="str"), + report_llg=dict(type="bool"), + immediate_leave=dict(type="bool"), + oif_routemap=dict(required=False, type="str"), + oif_ps=dict(required=False, type="raw"), + restart=dict(type="bool", default=False), + state=dict(choices=["present", "absent", "default"], default="present"), + ) + + mutually_exclusive = [("oif_ps", "oif_routemap")] + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + warnings = list() + + state = module.params["state"] + interface = module.params["interface"] + oif_routemap = module.params["oif_routemap"] + oif_ps = module.params["oif_ps"] + + intf_type = get_interface_type(interface) + if get_interface_mode(interface, intf_type, module) == "layer2": + module.fail_json(msg="this module only works on Layer 3 interfaces") + + existing = get_igmp_interface(module, interface) + existing_copy = existing.copy() + end_state = existing_copy + + if not existing.get("version"): + module.fail_json(msg="pim needs to be enabled on the interface") + + existing_oif_prefix_source = existing.get("oif_prefix_source") + # not json serializable + existing.pop("oif_prefix_source") + + if oif_routemap and existing_oif_prefix_source: + module.fail_json( + msg="Delete static-oif configurations on this " + "interface if you want to use a routemap", + ) + + if oif_ps and existing.get("oif_routemap"): + module.fail_json( + msg="Delete static-oif route-map configuration " + "on this interface if you want to config " + "static entries", + ) + + args = [ + "version", + "startup_query_interval", + "startup_query_count", + "robustness", + "querier_timeout", + "query_mrt", + "query_interval", + "last_member_qrt", + "last_member_query_count", + "group_timeout", + "report_llg", + "immediate_leave", + "oif_routemap", + ] + + changed = False + commands = [] + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + + CANNOT_ABSENT = [ + "version", + "startup_query_interval", + "startup_query_count", + "robustness", + "querier_timeout", + "query_mrt", + "query_interval", + "last_member_qrt", + "last_member_query_count", + "group_timeout", + "report_llg", + "immediate_leave", + ] + + if state == "absent": + for each in CANNOT_ABSENT: + if each in proposed: + module.fail_json( + msg="only params: " "oif_ps, oif_routemap can be used when " "state=absent", + ) + + # delta check for all params except oif_ps + delta = dict(set(proposed.items()).difference(existing.items())) + + if oif_ps: + if oif_ps == "default": + delta["oif_ps"] = [] + else: + delta["oif_ps"] = oif_ps + + if state == "present": + if delta: + command = config_igmp_interface(delta, existing, existing_oif_prefix_source) + if command: + commands.append(command) + + elif state == "default": + command = config_default_igmp_interface(existing, delta) + if command: + commands.append(command) + elif state == "absent": + command = None + if existing.get("oif_routemap") or existing_oif_prefix_source: + command = config_remove_oif(existing, existing_oif_prefix_source) + + if command: + commands.append(command) + + command = config_default_igmp_interface(existing, delta) + if command: + commands.append(command) + + cmds = [] + results = {} + if commands: + commands.insert(0, ["interface {0}".format(interface)]) + cmds = flatten_list(commands) + + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + load_config(module, cmds) + changed = True + end_state = get_igmp_interface(module, interface) + if "configure" in cmds: + cmds.pop(0) + + if module.params["restart"]: + cmd = {"command": "restart igmp", "output": "text"} + run_commands(module, cmd) + + results["proposed"] = proposed + results["existing"] = existing_copy + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + results["end_state"] = end_state + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py new file mode 100644 index 00000000..6d14ba17 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py @@ -0,0 +1,319 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_igmp_snooping +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages IGMP snooping global configuration. +description: +- Manages IGMP snooping global configuration. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- When C(state=default), params will be reset to a default state. +- C(group_timeout) also accepts I(never) as an input. +options: + snooping: + description: + - Enables/disables IGMP snooping on the switch. + type: bool + group_timeout: + description: + - Group membership timeout value for all VLANs on the device. Accepted values + are integer in range 1-10080, I(never) and I(default). + type: str + link_local_grp_supp: + description: + - Global link-local groups suppression. + type: bool + report_supp: + description: + - Global IGMPv1/IGMPv2 Report Suppression. + type: bool + v3_report_supp: + description: + - Global IGMPv3 Report Suppression and Proxy Reporting. + type: bool + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - default + type: str +""" + +EXAMPLES = """ +# ensure igmp snooping params supported in this module are in there default state +- cisco.nxos.nxos_igmp_snooping: + state: default + +# ensure following igmp snooping params are in the desired state +- cisco.nxos.nxos_igmp_snooping: + group_timeout: never + snooping: true + link_local_grp_supp: false + optimize_mcast_flood: false + report_supp: true + v3_report_supp: true +""" + +RETURN = """ +commands: + description: command sent to the device + returned: always + type: list + sample: ["ip igmp snooping link-local-groups-suppression", + "ip igmp snooping group-timeout 50", + "no ip igmp snooping report-suppression", + "no ip igmp snooping v3-report-suppression", + "no ip igmp snooping"] +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module, output="text"): + command = {"command": command, "output": output} + + return run_commands(module, [command]) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_group_timeout(config): + match = re.search(r" Group timeout configured: (\S+)", config, re.M) + if match: + value = match.group(1) + else: + value = "" + return value + + +def get_igmp_snooping(module): + command = "show ip igmp snooping" + existing = {} + + try: + body = execute_show_command(command, module, output="json")[0] + except IndexError: + body = [] + + if body: + snooping = str(body.get("enabled")).lower() + if "none" in snooping: + snooping = str(body.get("GlobalSnoopEnabled")).lower() + if snooping == "true" or snooping == "enabled" or snooping == "yes": + existing["snooping"] = True + else: + existing["snooping"] = False + + report_supp = str(body.get("grepsup")).lower() + if "none" in report_supp: + report_supp = str(body.get("GlobalReportSupression")).lower() + if report_supp == "true" or report_supp == "enabled": + existing["report_supp"] = True + else: + existing["report_supp"] = False + + link_local_grp_supp = str(body.get("glinklocalgrpsup")).lower() + if "none" in link_local_grp_supp: + link_local_grp_supp = str(body.get("GlobalLinkLocalGroupSupression")).lower() + if link_local_grp_supp == "true" or link_local_grp_supp == "enabled": + existing["link_local_grp_supp"] = True + else: + existing["link_local_grp_supp"] = False + + v3_report_supp = str(body.get("gv3repsup")).lower() + if "none" in v3_report_supp: + v3_report_supp = str(body.get("GlobalV3ReportSupression")).lower() + if v3_report_supp == "true" or v3_report_supp == "enabled": + existing["v3_report_supp"] = True + else: + existing["v3_report_supp"] = False + + command = "show ip igmp snooping" + body = execute_show_command(command, module)[0] + if body: + existing["group_timeout"] = get_group_timeout(body) + + return existing + + +def config_igmp_snooping(delta, existing, default=False): + CMDS = { + "snooping": "ip igmp snooping", + "group_timeout": "ip igmp snooping group-timeout {}", + "link_local_grp_supp": "ip igmp snooping link-local-groups-suppression", + "v3_report_supp": "ip igmp snooping v3-report-suppression", + "report_supp": "ip igmp snooping report-suppression", + } + + commands = [] + command = None + gt_command = None + for key, value in delta.items(): + if value: + if default and key == "group_timeout": + if existing.get(key): + gt_command = "no " + CMDS.get(key).format(existing.get(key)) + elif value == "default" and key == "group_timeout": + if existing.get(key): + command = "no " + CMDS.get(key).format(existing.get(key)) + else: + command = CMDS.get(key).format(value) + else: + command = "no " + CMDS.get(key).format(value) + + if command: + commands.append(command) + command = None + + if gt_command: + # ensure that group-timeout command is configured last + commands.append(gt_command) + return commands + + +def get_igmp_snooping_defaults(): + group_timeout = "dummy" + report_supp = True + link_local_grp_supp = True + v3_report_supp = False + snooping = True + + args = dict( + snooping=snooping, + link_local_grp_supp=link_local_grp_supp, + report_supp=report_supp, + v3_report_supp=v3_report_supp, + group_timeout=group_timeout, + ) + + default = dict((param, value) for (param, value) in args.items() if value is not None) + + return default + + +def igmp_snooping_gt_dependency(command, existing, module): + # group-timeout will fail if igmp snooping is disabled + gt = [i for i in command if i.startswith("ip igmp snooping group-timeout")] + if gt: + if "no ip igmp snooping" in command or ( + existing["snooping"] is False and "ip igmp snooping" not in command + ): + msg = "group-timeout cannot be enabled or changed when ip igmp snooping is disabled" + module.fail_json(msg=msg) + else: + # ensure that group-timeout command is configured last + command.remove(gt[0]) + command.append(gt[0]) + + +def main(): + argument_spec = dict( + snooping=dict(required=False, type="bool"), + group_timeout=dict(required=False, type="str"), + link_local_grp_supp=dict(required=False, type="bool"), + report_supp=dict(required=False, type="bool"), + v3_report_supp=dict(required=False, type="bool"), + state=dict(choices=["present", "default"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + snooping = module.params["snooping"] + link_local_grp_supp = module.params["link_local_grp_supp"] + report_supp = module.params["report_supp"] + v3_report_supp = module.params["v3_report_supp"] + group_timeout = module.params["group_timeout"] + state = module.params["state"] + + args = dict( + snooping=snooping, + link_local_grp_supp=link_local_grp_supp, + report_supp=report_supp, + v3_report_supp=v3_report_supp, + group_timeout=group_timeout, + ) + + proposed = dict((param, value) for (param, value) in args.items() if value is not None) + + existing = get_igmp_snooping(module) + + commands = [] + if state == "present": + delta = dict(set(proposed.items()).difference(existing.items())) + if delta: + command = config_igmp_snooping(delta, existing) + if command: + if group_timeout: + igmp_snooping_gt_dependency(command, existing, module) + commands.append(command) + elif state == "default": + proposed = get_igmp_snooping_defaults() + delta = dict(set(proposed.items()).difference(existing.items())) + if delta: + command = config_igmp_snooping(delta, existing, default=True) + if command: + commands.append(command) + + cmds = flatten_list(commands) + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + if "configure" in cmds: + cmds.pop(0) + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py new file mode 100644 index 00000000..2327ab54 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py @@ -0,0 +1,594 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_install_os +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Set boot options like boot, kickstart image and issu. +description: +- Install an operating system by setting the boot options like boot image and kickstart + image and optionally select to install using ISSU (In Server Software Upgrade). +version_added: 1.0.0 +notes: +- Tested against the following platforms and images - N9k 7.0(3)I4(6), 7.0(3)I5(3), + 7.0(3)I6(1), 7.0(3)I7(1), 7.0(3)F2(2), 7.0(3)F3(2) - N3k 6.0(2)A8(6), 6.0(2)A8(8), + 7.0(3)I6(1), 7.0(3)I7(1) - N7k 7.3(0)D1(1), 8.0(1), 8.1(1), 8.2(1) +- Tested against Cisco MDS NX-OS 9.2(1) +- This module requires both the ANSIBLE_PERSISTENT_CONNECT_TIMEOUT and ANSIBLE_PERSISTENT_COMMAND_TIMEOUT + timers to be set to 600 seconds or higher. The module will exit if the timers are + not set properly. +- When using connection local, ANSIBLE_PERSISTENT_CONNECT_TIMEOUT and ANSIBLE_PERSISTENT_COMMAND_TIMEOUT + can only be set using ENV variables or the ansible.cfg file. +- Do not include full file paths, just the name of the file(s) stored on the top level + flash directory. +- This module attempts to install the software immediately, which may trigger a reboot. +- In check mode, the module will indicate if an upgrade is needed and whether or not + the upgrade is disruptive or non-disruptive(ISSU). +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbibo (@GGabriele) +options: + system_image_file: + description: + - Name of the system (or combined) image file on flash. + required: true + type: str + kickstart_image_file: + description: + - Name of the kickstart image file on flash. (Not required on all Nexus platforms) + type: str + issu: + description: + - Upgrade using In Service Software Upgrade (ISSU). (Supported on N5k, N7k, N9k + platforms) + - Selecting 'required' or 'yes' means that upgrades will only proceed if the switch + is capable of ISSU. + - Selecting 'desired' means that upgrades will use ISSU if possible but will fall + back to disruptive upgrade if needed. + - Selecting 'no' means do not use ISSU. Forced disruptive. + choices: + - "required" + - "desired" + - "yes" + - "no" + default: "no" + type: str +""" + +EXAMPLES = """ +- name: Install OS on N9k + check_mode: no + cisco.nxos.nxos_install_os: + system_image_file: nxos.7.0.3.I6.1.bin + issu: desired + +- name: Wait for device to come back up with new image + wait_for: + port: 22 + state: started + timeout: 500 + delay: 60 + host: '{{ inventory_hostname }}' + +- name: Check installed OS for newly installed version + nxos_command: + commands: [show version | json] + register: output +- assert: + that: + - output['stdout'][0]['kickstart_ver_str'] == '7.0(3)I6(1)' +""" + +RETURN = """ +install_state: + description: Boot and install information. + returned: always + type: dict + sample: { + "install_state": [ + "Compatibility check is done:", + "Module bootable Impact Install-type Reason", + "------ -------- -------------- ------------ ------", + " 1 yes non-disruptive reset ", + "Images will be upgraded according to following table:", + "Module Image Running-Version(pri:alt) New-Version Upg-Required", + "------ ---------- ---------------------------------------- -------------------- ------------", + " 1 nxos 7.0(3)I6(1) 7.0(3)I7(1) yes", + " 1 bios v4.4.0(07/12/2017) v4.4.0(07/12/2017) no" + ], + } +""" + + +import re + +from time import sleep + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +# Output options are 'text' or 'json' +def execute_show_command(module, command, output="text"): + cmds = [{"command": command, "output": output}] + + return run_commands(module, cmds) + + +def get_platform(module): + """Determine platform type""" + data = execute_show_command(module, "show inventory", "json") + pid = data[0]["TABLE_inv"]["ROW_inv"][0]["productid"] + + if re.search(r"N3K", pid): + type = "N3K" + elif re.search(r"N5K", pid): + type = "N5K" + elif re.search(r"N6K", pid): + type = "N6K" + elif re.search(r"N7K", pid): + type = "N7K" + elif re.search(r"N9K", pid): + type = "N9K" + else: + type = "unknown" + + return type + + +def parse_show_install(data): + """Helper method to parse the output of the 'show install all impact' or + 'install all' commands. + + Sample Output: + + Installer will perform impact only check. Please wait. + + Verifying image bootflash:/nxos.7.0.3.F2.2.bin for boot variable "nxos". + [####################] 100% -- SUCCESS + + Verifying image type. + [####################] 100% -- SUCCESS + + Preparing "bios" version info using image bootflash:/nxos.7.0.3.F2.2.bin. + [####################] 100% -- SUCCESS + + Preparing "nxos" version info using image bootflash:/nxos.7.0.3.F2.2.bin. + [####################] 100% -- SUCCESS + + Performing module support checks. + [####################] 100% -- SUCCESS + + Notifying services about system upgrade. + [####################] 100% -- SUCCESS + + + + Compatibility check is done: + Module bootable Impact Install-type Reason + ------ -------- -------------- ------------ ------ + 8 yes disruptive reset Incompatible image for ISSU + 21 yes disruptive reset Incompatible image for ISSU + + + Images will be upgraded according to following table: + Module Image Running-Version(pri:alt) New-Version Upg-Required + ------ ---------- ---------------------------------------- ------------ + 8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes + 8 bios v01.17 v01.17 no + 21 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes + 21 bios v01.70 v01.70 no + """ + if len(data) > 0: + data = massage_install_data(data) + ud = {"raw": data} + ud["processed"] = [] + ud["disruptive"] = False + ud["upgrade_needed"] = False + ud["error"] = False + ud["invalid_command"] = False + ud["install_in_progress"] = False + ud["server_error"] = False + ud["upgrade_succeeded"] = False + ud["use_impact_data"] = False + + # Check for server errors + if isinstance(data, int): + if data == -1: + ud["server_error"] = True + elif data >= 500: + ud["server_error"] = True + elif data == -32603: + ud["server_error"] = True + elif data == 1: + ud["server_error"] = True + return ud + else: + ud["list_data"] = data.split("\n") + + for x in ud["list_data"]: + # Check for errors and exit if found. + if re.search(r"Pre-upgrade check failed", x): + ud["error"] = True + break + if re.search(r"[I|i]nvalid command", x): + ud["invalid_command"] = True + ud["error"] = True + break + if re.search(r"No install all data found", x): + ud["error"] = True + break + + # Check for potentially transient conditions + if re.search(r"Another install procedure may\s*be in progress", x): + ud["install_in_progress"] = True + break + if re.search(r"Backend processing error", x): + ud["server_error"] = True + break + if re.search(r"timed out", x): + ud["server_error"] = True + break + if re.search(r"^(-1|5\d\d)$", x): + ud["server_error"] = True + break + + # Check for messages indicating a successful upgrade. + if re.search(r"Finishing the upgrade", x): + ud["upgrade_succeeded"] = True + break + if re.search(r"Install has been successful", x): + ud["upgrade_succeeded"] = True + break + if re.search(r"Switching over onto standby", x): + ud["upgrade_succeeded"] = True + break + + # We get these messages when the upgrade is non-disruptive and + # we loose connection with the switchover but far enough along that + # we can be confident the upgrade succeeded. + if re.search(r"timeout .*trying to send command: install", x): + ud["upgrade_succeeded"] = True + ud["use_impact_data"] = True + break + if re.search(r"[C|c]onnection failure: timed out", x): + ud["upgrade_succeeded"] = True + ud["use_impact_data"] = True + break + + # Begin normal parsing. + if re.search(r"----|Module|Images will|Compatibility", x): + ud["processed"].append(x) + continue + # Check to see if upgrade will be disruptive or non-disruptive and + # build dictionary of individual modules and their status. + # Sample Line: + # + # Module bootable Impact Install-type Reason + # ------ -------- ---------- ------------ ------ + # 8 yes disruptive reset Incompatible image + rd = r"(\d+)\s+(\S+)\s+(disruptive|non-disruptive)\s+(\S+)" + mo = re.search(rd, x) + if mo: + ud["processed"].append(x) + key = "m%s" % mo.group(1) + field = "disruptive" + if mo.group(3) == "non-disruptive": + ud[key] = {field: False} + else: + ud[field] = True + ud[key] = {field: True} + field = "bootable" + if mo.group(2) == "yes": + ud[key].update({field: True}) + else: + ud[key].update({field: False}) + continue + + # Check to see if switch needs an upgrade and build a dictionary + # of individual modules and their individual upgrade status. + # Sample Line: + # + # Module Image Running-Version(pri:alt) New-Version Upg-Required + # ------ ----- ---------------------------------------- ------------ + # 8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes + mo = re.search(r"(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(yes|no)", x) + if mo: + ud["processed"].append(x) + key = "m%s_%s" % (mo.group(1), mo.group(2)) + field = "upgrade_needed" + if mo.group(5) == "yes": + ud[field] = True + ud[key] = {field: True} + else: + ud[key] = {field: False} + continue + + return ud + + +def massage_install_data(data): + # Transport cli returns a list containing one result item. + # Transport nxapi returns a list containing two items. The second item + # contains the data we are interested in. + default_error_msg = "No install all data found" + if len(data) == 1: + result_data = data[0] + elif len(data) == 2: + result_data = data[1] + else: + result_data = default_error_msg + + # Further processing may be needed for result_data + if len(data) == 2 and isinstance(data[1], dict): + if "clierror" in data[1].keys(): + result_data = data[1]["clierror"] + elif "code" in data[1].keys() and data[1]["code"] == "500": + # We encountered a backend processing error for nxapi + result_data = data[1]["msg"] + else: + result_data = default_error_msg + return result_data + + +def build_install_cmd_set(issu, image, kick, type, force=True): + commands = ["terminal dont-ask"] + + # Different NX-OS platforms behave differently for + # disruptive and non-disruptive upgrade paths. + # + # 1) Combined kickstart/system image: + # * Use option 'non-disruptive' for issu. + # * Omit option 'non-disruptive' for disruptive upgrades. + # 2) Separate kickstart + system images. + # * Omit hidden 'force' option for issu. + # * Use hidden 'force' option for disruptive upgrades. + # * Note: Not supported on all platforms + if re.search(r"required|desired|yes", issu): + if kick is None: + issu_cmd = "non-disruptive" + else: + issu_cmd = "" + else: + if kick is None: + issu_cmd = "" + else: + issu_cmd = "force" if force else "" + + if type == "impact": + rootcmd = "show install all impact" + # The force option is not available for the impact command. + if kick: + issu_cmd = "" + else: + rootcmd = "install all" + if kick is None: + commands.append("%s nxos %s %s" % (rootcmd, image, issu_cmd)) + else: + commands.append("%s %s system %s kickstart %s" % (rootcmd, issu_cmd, image, kick)) + + return commands + + +def parse_show_version(data): + version_data = {"raw": data[0].split("\n")} + version_data["version"] = "" + version_data["error"] = False + for x in version_data["raw"]: + mo = re.search(r"(kickstart|system|NXOS):\s+version\s+(\S+)", x) + if mo: + version_data["version"] = mo.group(2) + continue + + if version_data["version"] == "": + version_data["error"] = True + + return version_data + + +def check_mode_legacy(module, issu, image, kick=None): + """Some platforms/images/transports don't support the 'install all impact' + command so we need to use a different method.""" + current = execute_show_command(module, "show version", "json")[0] + # Call parse_show_data on empty string to create the default upgrade + # data structure dictionary + data = parse_show_install("") + upgrade_msg = "No upgrade required" + + # Process System Image + data["error"] = False + tsver = "show version image bootflash:%s" % image + data["upgrade_cmd"] = [tsver] + target_image = parse_show_version(execute_show_command(module, tsver)) + if target_image["error"]: + data["error"] = True + data["raw"] = target_image["raw"] + if current["kickstart_ver_str"] != target_image["version"] and not data["error"]: + data["upgrade_needed"] = True + data["disruptive"] = True + upgrade_msg = "Switch upgraded: system: %s" % tsver + + # Process Kickstart Image + if kick is not None and not data["error"]: + tkver = "show version image bootflash:%s" % kick + data["upgrade_cmd"].append(tsver) + target_kick = parse_show_version(execute_show_command(module, tkver)) + if target_kick["error"]: + data["error"] = True + data["raw"] = target_kick["raw"] + if current["kickstart_ver_str"] != target_kick["version"] and not data["error"]: + data["upgrade_needed"] = True + data["disruptive"] = True + upgrade_msg = upgrade_msg + " kickstart: %s" % tkver + + data["list_data"] = data["raw"] + data["processed"] = upgrade_msg + return data + + +def check_mode_nextgen(module, issu, image, kick=None): + """Use the 'install all impact' command for check_mode""" + opts = {"ignore_timeout": True} + commands = build_install_cmd_set(issu, image, kick, "impact") + data = parse_show_install(load_config(module, commands, True, opts)) + # If an error is encountered when issu is 'desired' then try again + # but set issu to 'no' + if data["error"] and issu == "desired": + issu = "no" + commands = build_install_cmd_set(issu, image, kick, "impact") + # The system may be busy from the previous call to check_mode so loop + # until it's done. + data = check_install_in_progress(module, commands, opts) + if data["server_error"]: + data["error"] = True + data["upgrade_cmd"] = commands + return data + + +def check_install_in_progress(module, commands, opts): + for attempt in range(20): + data = parse_show_install(load_config(module, commands, True, opts)) + if data["install_in_progress"]: + sleep(1) + continue + break + return data + + +def check_mode(module, issu, image, kick=None): + """Check switch upgrade impact using 'show install all impact' command""" + data = check_mode_nextgen(module, issu, image, kick) + if data["server_error"]: + # We encountered an unrecoverable error in the attempt to get upgrade + # impact data from the 'show install all impact' command. + # Fallback to legacy method. + data = check_mode_legacy(module, issu, image, kick) + if data["invalid_command"]: + # If we are upgrading from a device running a separate kickstart and + # system image the impact command will fail. + # Fallback to legacy method. + data = check_mode_legacy(module, issu, image, kick) + return data + + +def do_install_all(module, issu, image, kick=None): + """Perform the switch upgrade using the 'install all' command""" + impact_data = check_mode(module, issu, image, kick) + if module.check_mode: + # Check mode set in the playbook so just return the impact data. + msg = "*** SWITCH WAS NOT UPGRADED: IMPACT DATA ONLY ***" + impact_data["processed"].append(msg) + return impact_data + if impact_data["error"]: + # Check mode discovered an error so return with this info. + return impact_data + elif not impact_data["upgrade_needed"]: + # The switch is already upgraded. Nothing more to do. + return impact_data + else: + # If we get here, check_mode returned no errors and the switch + # needs to be upgraded. + if impact_data["disruptive"]: + # Check mode indicated that ISSU is not possible so issue the + # upgrade command without the non-disruptive flag unless the + # playbook specified issu: yes/required. + if issu == "yes": + msg = "ISSU/ISSD requested but impact data indicates ISSU/ISSD is not possible" + module.fail_json(msg=msg, raw_data=impact_data["list_data"]) + else: + issu = "no" + + commands = build_install_cmd_set(issu, image, kick, "install") + opts = {"ignore_timeout": True} + # The system may be busy from the call to check_mode so loop until + # it's done. + upgrade = check_install_in_progress(module, commands, opts) + if upgrade["invalid_command"] and "force" in commands[1]: + # Not all platforms support the 'force' keyword. Check for this + # condition and re-try without the 'force' keyword if needed. + commands = build_install_cmd_set(issu, image, kick, "install", False) + upgrade = check_install_in_progress(module, commands, opts) + upgrade["upgrade_cmd"] = commands + + # Special case: If we encounter a server error at this stage + # it means the command was sent and the upgrade was started but + # we will need to use the impact data instead of the current install + # data. + if upgrade["server_error"]: + upgrade["upgrade_succeeded"] = True + upgrade["use_impact_data"] = True + + if upgrade["use_impact_data"]: + if upgrade["upgrade_succeeded"]: + upgrade = impact_data + upgrade["upgrade_succeeded"] = True + else: + upgrade = impact_data + upgrade["upgrade_succeeded"] = False + + if not upgrade["upgrade_succeeded"]: + upgrade["error"] = True + return upgrade + + +def main(): + argument_spec = dict( + system_image_file=dict(required=True), + kickstart_image_file=dict(required=False), + issu=dict(choices=["required", "desired", "no", "yes"], default="no"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + # Get system_image_file(sif), kickstart_image_file(kif) and + # issu settings from module params. + sif = module.params["system_image_file"] + kif = module.params["kickstart_image_file"] + issu = module.params["issu"] + + if re.search(r"(yes|required)", issu): + issu = "yes" + + if kif == "null" or kif == "": + kif = None + + install_result = do_install_all(module, issu, sif, kick=kif) + if install_result["error"]: + cmd = install_result["upgrade_cmd"] + msg = "Failed to upgrade device using command: %s" % cmd + module.fail_json(msg=msg, raw_data=install_result["list_data"]) + + state = install_result["processed"] + changed = install_result["upgrade_needed"] + module.exit_json(changed=changed, install_state=state, warnings=warnings) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py new file mode 100644 index 00000000..f03f746b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py @@ -0,0 +1,455 @@ +#!/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 nxos_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_interfaces +short_description: Interfaces resource module +description: This module manages the interface attributes of NX-OS interfaces. +version_added: 1.0.0 +author: Trishna Guha (@trishnaguha) +notes: +- Tested against NXOS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section ^interface) + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dictionary of interface options + type: list + elements: dict + suboptions: + name: + description: + - Full name of interface, e.g. Ethernet1/1, port-channel10. + type: str + required: true + description: + description: + - Interface description. + type: str + enabled: + description: + - Administrative state of the interface. Set the value to C(true) to administratively + enable the interface or C(false) to disable it + type: bool + speed: + description: + - Interface link speed. Applicable for Ethernet interfaces only. + type: str + mode: + description: + - Manage Layer2 or Layer3 state of the interface. Applicable for Ethernet + and port channel interfaces only. + choices: + - layer2 + - layer3 + type: str + mtu: + description: + - MTU for a specific interface. Must be an even number between 576 and 9216. + Applicable for Ethernet interfaces only. + type: str + duplex: + description: + - Interface link status. Applicable for Ethernet interfaces only. + type: str + choices: + - full + - half + - auto + ip_forward: + description: + - Enable or disable IP forward feature on SVIs. Set the value to C(true) to + enable or C(false) to disable. + type: bool + fabric_forwarding_anycast_gateway: + description: + - Associate SVI with anycast gateway under VLAN configuration mode. Applicable + for SVI interfaces only. + type: bool + state: + description: + - The state of the configuration after module completion + - The state I(rendered) considers the system default mode for interfaces to be + "Layer 3" and the system default state for interfaces to be shutdown. + - The state I(purged) negates virtual interfaces that are specified in task + from running-config. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + - purged + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# interface Ethernet1/1 +# description testing +# mtu 1800 + +- name: Merge provided configuration with device configuration + cisco.nxos.nxos_interfaces: + config: + - name: Ethernet1/1 + description: Configured by Ansible + enabled: true + - name: Ethernet1/2 + description: Configured by Ansible Network + enabled: false + state: merged + +# After state: +# ------------ +# +# interface Ethernet1/1 +# description Configured by Ansible +# no shutdown +# mtu 1800 +# interface Ethernet2 +# description Configured by Ansible Network +# shutdown + + +# Using replaced + +# Before state: +# ------------- +# +# interface Ethernet1/1 +# description Interface 1/1 +# interface Ethernet1/2 + +- name: Replaces device configuration of listed interfaces with provided configuration + cisco.nxos.nxos_interfaces: + config: + - name: Ethernet1/1 + description: Configured by Ansible + enabled: true + mtu: 2000 + - name: Ethernet1/2 + description: Configured by Ansible Network + enabled: false + mode: layer2 + state: replaced + +# After state: +# ------------ +# +# interface Ethernet1/1 +# description Configured by Ansible +# no shutdown +# mtu 1500 +# interface Ethernet2/2 +# description Configured by Ansible Network +# shutdown +# switchport + + +# Using overridden + +# Before state: +# ------------- +# +# interface Ethernet1/1 +# description Interface Ethernet1/1 +# interface Ethernet1/2 +# interface mgmt0 +# description Management interface +# ip address dhcp + +- name: Override device configuration of all interfaces with provided configuration + cisco.nxos.nxos_interfaces: + config: + - name: Ethernet1/1 + enabled: true + - name: Ethernet1/2 + description: Configured by Ansible Network + enabled: false + state: overridden + +# After state: +# ------------ +# +# interface Ethernet1/1 +# interface Ethernet1/2 +# description Configured by Ansible Network +# shutdown +# interface mgmt0 +# ip address dhcp + + +# Using deleted + +# Before state: +# ------------- +# +# interface Ethernet1/1 +# description Interface Ethernet1/1 +# interface Ethernet1/2 +# interface mgmt0 +# description Management interface +# ip address dhcp + +- name: Delete or return interface parameters to default settings + cisco.nxos.nxos_interfaces: + config: + - name: Ethernet1/1 + state: deleted + +# After state: +# ------------ +# +# interface Ethernet1/1 +# interface Ethernet1/2 +# interface mgmt0 +# description Management interface +# ip address dhcp + +# Using rendered + +- name: Use rendered state to convert task input to device specific commands + cisco.nxos.nxos_interfaces: + config: + - name: Ethernet1/1 + description: outbound-intf + mode: layer3 + speed: 100 + - name: Ethernet1/2 + mode: layer2 + enabled: true + duplex: full + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "interface Ethernet1/1" +# - "description outbound-intf" +# - "speed 100" +# - "interface Ethernet1/2" +# - "switchport" +# - "duplex full" +# - "no shutdown" + +# Using parsed + +# parsed.cfg +# ------------ +# interface Ethernet1/800 +# description test-1 +# speed 1000 +# shutdown +# no switchport +# duplex half +# interface Ethernet1/801 +# description test-2 +# switchport +# no shutdown +# mtu 1800 + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_interfaces: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# - description: "test-1" +# duplex: "half" +# enabled: false +# mode: "layer3" +# name: "Ethernet1/800" +# speed: "1000" +# +# - description: "test-2" +# enabled: true +# mode: "layer2" +# mtu: "1800" +# name: "Ethernet1/801" + +# Using gathered + +# Existing device config state +# ----------------------------- +# interface Ethernet1/1 +# description outbound-intf +# switchport +# no shutdown +# interface Ethernet1/2 +# description intf-l3 +# speed 1000 +# interface Ethernet1/3 +# interface Ethernet1/4 +# interface Ethernet1/5 + +- name: Gather interfaces facts from the device using nxos_interfaces + cisco.nxos.nxos_interfaces: + state: gathered + +# Task output (redacted) +# ----------------------- +# - name: Ethernet1/1 +# description: outbound-intf +# mode: layer2 +# enabled: True +# - name: Ethernet1/2 +# description: intf-l3 +# speed: "1000" + +# Using purged + +# Existing device config state +# ----------------------------- +# interface Vlan1 +# interface Vlan42 +# mtu 1800 +# interface port-channel10 +# interface port-channel11 +# interface Ethernet1/1 +# interface Ethernet1/2 +# interface Ethernet1/2.100 +# description sub-intf + +- name: Purge virtual interfaces from running-config + cisco.nxos.nxos_interfaces: + config: + - name: Vlan42 + - name: port-channel10 + - name: Ethernet1/2.100 + state: purged + +# Task output +# ------------ +# before: +# - name: Vlan1 +# - mtu: '1800' +# name: Vlan42 +# - name: port-channel10 +# - name: port-channel11 +# - name: Ethernet1/1 +# - name: Ethernet1/2 +# - description: sub-intf +# name: Ethernet1/2.100 +# +# commands: +# - no interface port-channel10 +# - no interface Ethernet1/2.100 +# - no interface Vlan42 +# +# after: +# - name: Vlan1 +# - name: port-channel11 +# - name: Ethernet1/1 +# - name: Ethernet1/2 +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: ['interface Ethernet1/1', 'mtu 1800'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.interfaces.interfaces import ( + InterfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.interfaces.interfaces import ( + Interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=InterfacesArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = Interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py new file mode 100644 index 00000000..3e73c432 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py @@ -0,0 +1,408 @@ +#!/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 nxos_l2_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_l2_interfaces +short_description: L2 interfaces resource module +description: This module manages Layer-2 interfaces attributes of NX-OS Interfaces. +version_added: 1.0.0 +author: Trishna Guha (@trishnaguha) +notes: +- Tested against NXOS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section ^interface). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dictionary of Layer-2 interface options + type: list + elements: dict + suboptions: + name: + description: + - Full name of interface, i.e. Ethernet1/1. + type: str + required: true + access: + description: + - Switchport mode access command to configure the interface as a Layer-2 access. + type: dict + suboptions: + vlan: + description: + - Configure given VLAN in access port. It's used as the access VLAN ID. + type: int + trunk: + description: + - Switchport mode trunk command to configure the interface as a Layer-2 trunk. + type: dict + suboptions: + native_vlan: + description: + - Native VLAN to be configured in trunk port. It is used as the trunk + native VLAN ID. + type: int + allowed_vlans: + description: + - List of allowed VLANs in a given trunk port. These are the only VLANs + that will be configured on the trunk. + type: str + mode: + description: + - Mode in which interface needs to be configured. + - Access mode is not shown in interface facts, so idempotency will not be + maintained for switchport mode access and every time the output will come + as changed=True. + type: str + choices: + - dot1q-tunnel + - access + - trunk + - fex-fabric + - fabricpath + state: + description: + - The state of the configuration after module completion. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# interface Ethernet1/1 +# switchport access vlan 20 +# interface Ethernet1/2 +# switchport trunk native vlan 20 +# interface mgmt0 +# ip address dhcp +# ipv6 address auto-config + +- name: Merge provided configuration with device configuration. + cisco.nxos.nxos_l2_interfaces: + config: + - name: Ethernet1/1 + trunk: + native_vlan: 10 + allowed_vlans: 2,4,15 + - name: Ethernet1/2 + access: + vlan: 30 + state: merged + +# After state: +# ------------ +# +# interface Ethernet1/1 +# switchport trunk native vlan 10 +# switchport trunk allowed vlans 2,4,15 +# interface Ethernet1/2 +# switchport access vlan 30 +# interface mgmt0 +# ip address dhcp +# ipv6 address auto-config + + +# Using replaced + +# Before state: +# ------------- +# +# interface Ethernet1/1 +# switchport access vlan 20 +# interface Ethernet1/2 +# switchport trunk native vlan 20 +# interface mgmt0 +# ip address dhcp +# ipv6 address auto-config + +- name: Replace device configuration of specified L2 interfaces with provided configuration. + cisco.nxos.nxos_l2_interfaces: + config: + - name: Ethernet1/1 + trunk: + native_vlan: 20 + allowed_vlans: 5-10, 15 + state: replaced + +# After state: +# ------------ +# +# interface Ethernet1/1 +# switchport trunk native vlan 20 +# switchport trunk allowed vlan 5-10,15 +# interface Ethernet1/2 +# switchport trunk native vlan 20 +# switchport mode trunk +# interface mgmt0 +# ip address dhcp +# ipv6 address auto-config + + +# Using overridden + +# Before state: +# ------------- +# +# interface Ethernet1/1 +# switchport access vlan 20 +# interface Ethernet1/2 +# switchport trunk native vlan 20 +# interface mgmt0 +# ip address dhcp +# ipv6 address auto-config + +- name: Override device configuration of all L2 interfaces on device with provided + configuration. + cisco.nxos.nxos_l2_interfaces: + config: + - name: Ethernet1/2 + access: + vlan: 30 + state: overridden + +# After state: +# ------------ +# +# interface Ethernet1/1 +# interface Ethernet1/2 +# switchport access vlan 30 +# interface mgmt0 +# ip address dhcp +# ipv6 address auto-config + + +# Using deleted + +# Before state: +# ------------- +# +# interface Ethernet1/1 +# switchport access vlan 20 +# interface Ethernet1/2 +# switchport trunk native vlan 20 +# interface mgmt0 +# ip address dhcp +# ipv6 address auto-config + +- name: Delete L2 attributes of given interfaces (Note This won't delete the interface + itself). + cisco.nxos.nxos_l2_interfaces: + config: + - name: Ethernet1/1 + - name: Ethernet1/2 + state: deleted + +# After state: +# ------------ +# +# interface Ethernet1/1 +# interface Ethernet1/2 +# interface mgmt0 +# ip address dhcp +# ipv6 address auto-config + +# Using rendered + +- name: Render platform specific configuration lines (without connecting to the device) + cisco.nxos.nxos_l2_interfaces: + config: + - name: Ethernet1/1 + trunk: + native_vlan: 10 + allowed_vlans: 2,4,15 + - name: Ethernet1/2 + access: + vlan: 30 + - name: Ethernet1/3 + trunk: + native_vlan: 20 + allowed_vlans: 5-10, 15 + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "interface Ethernet1/1" +# - "switchport trunk allowed vlan 2,4,15" +# - "switchport trunk native vlan 10" +# - "interface Ethernet1/2" +# - "switchport access vlan 30" +# - "interface Ethernet1/3" +# - "switchport trunk allowed vlan 5,6,7,8,9,10,15" +# - "switchport trunk native vlan 20" + +# Using parsed + +# parsed.cfg +# ------------ +# interface Ethernet1/800 +# switchport access vlan 18 +# switchport trunk allowed vlan 210 +# interface Ethernet1/801 +# switchport trunk allowed vlan 2,4,15 + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_l2_interfaces: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# - name: Ethernet1/800 +# access: +# vlan: 18 +# trunk: +# allowed_vlans: "210" +# - name: Ethernet1/801 +# trunk: +# allowed_vlans: "2,4,15" + +# Using gathered + +# Existing device config state +# ------------------------------- +# Nexus9kvI5# sh running-config | section ^interface +# interface Ethernet1/1 +# switchport access vlan 6 +# switchport trunk allowed vlan 200 +# interface Ethernet1/2 +# switchport trunk native vlan 10 + +- name: Gather l2_interfaces facts from the device using nxos_l2_interfaces + cisco.nxos.nxos_l2_interfaces: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# - name: "Ethernet1/1" +# access: +# vlan: 6 +# trunk: +# allowed_vlans: "200" +# +# - name: "Ethernet1/2" +# trunk: +# native_vlan: 10 +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: + - "interface Ethernet1/1" + - "switchport trunk allowed vlan 2,4,15" + - "switchport trunk native vlan 10" + - "interface Ethernet1/2" + - "switchport access vlan 30" + - "interface Ethernet1/3" + - "switchport trunk allowed vlan 5,6,7,8,9,10,15" + - "switchport trunk native vlan 20" +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.l2_interfaces.l2_interfaces import ( + L2_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.l2_interfaces.l2_interfaces import ( + L2_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=L2_interfacesArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = L2_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py new file mode 100644 index 00000000..7b5cb7a6 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py @@ -0,0 +1,411 @@ +#!/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 nxos_l3_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_l3_interfaces +short_description: L3 interfaces resource module +description: This module manages Layer-3 interfaces attributes of NX-OS Interfaces. +version_added: 1.0.0 +author: Trishna Guha (@trishnaguha) +notes: +- Tested against NXOS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^interface'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dictionary of Layer-3 interface options + type: list + elements: dict + suboptions: + name: + description: + - Full name of L3 interface, i.e. Ethernet1/1. + type: str + required: true + dot1q: + description: + - Configures IEEE 802.1Q VLAN encapsulation on a subinterface. + type: int + ipv4: + description: + - IPv4 address and attributes of the L3 interface. + type: list + elements: dict + suboptions: + address: + description: + - IPV4 address of the L3 interface. + type: str + tag: + description: + - URIB route tag value for local/direct routes. + type: int + secondary: + description: + - A boolean attribute to manage addition of secondary IP address. + type: bool + ipv6: + description: + - IPv6 address and attributes of the L3 interface. + type: list + elements: dict + suboptions: + address: + description: + - IPV6 address of the L3 interface. + type: str + tag: + description: + - URIB route tag value for local/direct routes. + type: int + redirects: + description: + - Enables/disables ipv4 redirects. + type: bool + ipv6_redirects: + description: + - Enables/disables ipv6 redirects. + type: bool + unreachables: + description: + - Enables/disables ip redirects + type: bool + evpn_multisite_tracking: + description: + - VxLAN evpn multisite Interface tracking. Supported only on selected model. + type: str + version_added: 1.1.0 + choices: + - fabric-tracking + - dci-tracking + state: + description: + - The state of the configuration after module completion. + - The state I(overridden) would override the IP address configuration + of all interfaces on the device with the provided configuration in + the task. Use caution with this state as you may loose access to the + device. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# interface Ethernet1/6 + +- name: Merge provided configuration with device configuration. + cisco.nxos.nxos_l3_interfaces: + config: + - name: Ethernet1/6 + ipv4: + - address: 192.168.1.1/24 + tag: 5 + - address: 10.1.1.1/24 + secondary: true + tag: 10 + ipv6: + - address: fd5d:12c9:2201:2::1/64 + tag: 6 + - name: Ethernet1/7.42 + dot1q: 42 + redirects: false + unreachables: false + state: merged + +# After state: +# ------------ +# +# interface Ethernet1/6 +# ip address 192.168.22.1/24 tag 5 +# ip address 10.1.1.1/24 secondary tag 10 +# interface Ethernet1/6 +# ipv6 address fd5d:12c9:2201:2::1/64 tag 6 +# interface Ethernet1/7.42 +# encapsulation dot1q 42 +# no ip redirects +# no ip unreachables + + +# Using replaced + +# Before state: +# ------------- +# +# interface Ethernet1/6 +# ip address 192.168.22.1/24 +# ipv6 address "fd5d:12c9:2201:1::1/64" + +- name: Replace device configuration of specified L3 interfaces with provided configuration. + cisco.nxos.nxos_l3_interfaces: + config: + - name: Ethernet1/6 + ipv4: + - address: 192.168.22.3/24 + state: replaced + +# After state: +# ------------ +# +# interface Ethernet1/6 +# ip address 192.168.22.3/24 + + +# Using overridden + +# Before state: +# ------------- +# +# interface Ethernet1/2 +# ip address 192.168.22.1/24 +# interface Ethernet1/6 +# ipv6 address "fd5d:12c9:2201:1::1/64" + +- name: Override device configuration of all L3 interfaces on device with provided + configuration. + cisco.nxos.nxos_l3_interfaces: + config: + - name: Ethernet1/2 + ipv4: 192.168.22.3/4 + state: overridden + +# After state: +# ------------ +# +# interface Ethernet1/2 +# ipv4 address 192.168.22.3/24 +# interface Ethernet1/6 + + +# Using deleted + +# Before state: +# ------------- +# +# interface Ethernet1/6 +# ip address 192.168.22.1/24 +# interface Ethernet1/2 +# ipv6 address "fd5d:12c9:2201:1::1/64" + +- name: Delete L3 attributes of given interfaces (This won't delete the interface + itself). + cisco.nxos.nxos_l3_interfaces: + config: + - name: Ethernet1/6 + - name: Ethernet1/2 + state: deleted + +# After state: +# ------------ +# +# interface Ethernet1/6 +# interface Ethernet1/2 + +# Using rendered + +- name: Use rendered state to convert task input to device specific commands + cisco.nxos.nxos_l3_interfaces: + config: + - name: Ethernet1/800 + ipv4: + - address: 192.168.1.100/24 + tag: 5 + - address: 10.1.1.1/24 + secondary: true + tag: 10 + - name: Ethernet1/800 + ipv6: + - address: fd5d:12c9:2201:2::1/64 + tag: 6 + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "interface Ethernet1/800" +# - "ip address 192.168.1.100/24 tag 5" +# - "ip address 10.1.1.1/24 secondary tag 10" +# - "interface Ethernet1/800" +# - "ipv6 address fd5d:12c9:2201:2::1/64 tag 6" + +# Using parsed + +# parsed.cfg +# ------------ +# interface Ethernet1/800 +# ip address 192.168.1.100/24 tag 5 +# ip address 10.1.1.1/24 secondary tag 10 +# no ip redirects +# interface Ethernet1/801 +# ipv6 address fd5d:12c9:2201:2::1/64 tag 6 +# ip unreachables +# interface mgmt0 +# ip address dhcp +# vrf member management + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_l3_interfaces: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- + +# parsed: +# - name: Ethernet1/800 +# ipv4: +# - address: 192.168.1.100/24 +# tag: 5 +# - address: 10.1.1.1/24 +# secondary: True +# tag: 10 +# redirects: False +# - name: Ethernet1/801 +# ipv6: +# - address: fd5d:12c9:2201:2::1/64 +# tag: 6 +# unreachables: True + +# Using gathered + +# Existing device config state +# ------------------------------- +# interface Ethernet1/1 +# ip address 192.0.2.100/24 +# interface Ethernet1/2 +# no ip redirects +# ip address 203.0.113.10/24 +# ip unreachables +# ipv6 address 2001:db8::1/32 + +- name: Gather l3_interfaces facts from the device using nxos_l3_interfaces + cisco.nxos.nxos_l3_interfaces: + state: gathered + +# Task output (redacted) +# ----------------------- + +# gathered: +# - name: Ethernet1/1 +# ipv4: +# - address: 192.0.2.100/24 +# - name: Ethernet1/2 +# ipv4: +# - address: 203.0.113.10/24 +# ipv6: +# - address: 2001:db8::1/32 +# redirects: False +# unreachables: True +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: ['interface Ethernet1/2', 'ip address 192.168.0.1/2'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.l3_interfaces.l3_interfaces import ( + L3_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.l3_interfaces.l3_interfaces import ( + L3_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=L3_interfacesArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = L3_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py new file mode 100644 index 00000000..23349113 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py @@ -0,0 +1,276 @@ +#!/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 nxos_lacp +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_lacp +short_description: LACP resource module +description: This module manages Global Link Aggregation Control Protocol (LACP) on + NX-OS devices. +version_added: 1.0.0 +author: Trishna Guha (@trishnaguha) +notes: +- Tested against NXOS 7.3.(0)D1(1) on VIRL. +- Unsupported for Cisco MDS +- Feature lacp should be enabled for this module. +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | include lacp). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: LACP global options. + type: dict + suboptions: + system: + description: + - LACP system options + type: dict + suboptions: + priority: + description: + - The system priority to use in LACP negotiations. + type: int + mac: + description: + - MAC address to be used for the LACP Protocol exchanges + type: dict + suboptions: + address: + description: + - MAC-address (FORMAT :xxxx.xxxx.xxxx). + type: str + role: + description: + - The role for the Switch. + type: str + choices: + - primary + - secondary + state: + description: + - The state of the configuration after module completion. + type: str + choices: + - merged + - replaced + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# + +- name: Merge provided configuration with device configuration. + cisco.nxos.nxos_lacp: + config: + system: + priority: 10 + mac: + address: 00c1.4c00.bd15 + state: merged + +# After state: +# ------------ +# +# lacp system-priority 10 +# lacp system-mac 00c1.4c00.bd15 + + +# Using replaced + +# Before state: +# ------------- +# +# lacp system-priority 10 + +- name: Replace device global lacp configuration with the given configuration. + cisco.nxos.nxos_lacp: + config: + system: + mac: + address: 00c1.4c00.bd15 + state: replaced + +# After state: +# ------------ +# +# lacp system-mac 00c1.4c00.bd15 + + +# Using deleted + +# Before state: +# ------------- +# +# lacp system-priority 10 + +- name: Delete global LACP configurations. + cisco.nxos.nxos_lacp: + state: deleted + +# After state: +# ------------ +# + +# Using rendered + +- name: Render platform specific configuration lines (without connecting to the device) + cisco.nxos.nxos_lacp: + config: + system: + priority: 10 + mac: + address: 00c1.4c00.bd15 + role: secondary + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "lacp system-priority 10" +# - "lacp system-mac 00c1.4c00.bd15 role secondary" + +# Using parsed + +# parsed.cfg +# ------------ +# lacp system-priority 10 +# lacp system-mac 00c1.4c00.bd15 role secondary + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_lacp: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# system: +# priority: 10 +# mac: +# address: 00c1.4c00.bd15 +# role: secondary + +# Using gathered + +# Existing device config state +# ------------------------------- +# Nexus9000v# show running-config | include lacp +# lacp system-priority 11 +# lacp system-mac 00c1.4c00.bd15 role primary + +- name: Gather lacp facts from the device using nxos_lacp + cisco.nxos.nxos_lacp: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# system: +# priority: 11 +# mac: +# address: 00c1.4c00.bd15 +# role: primary +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['lacp system-priority 15', 'lacp system-mac 00c1.4c00.bd15 role primary'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp.lacp import ( + LacpArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lacp.lacp import Lacp + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=LacpArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = Lacp(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py new file mode 100644 index 00000000..98e5a633 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py @@ -0,0 +1,381 @@ +#!/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 nxos_lacp_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_lacp_interfaces +short_description: LACP interfaces resource module +description: This module manages Link Aggregation Control Protocol (LACP) attributes + of NX-OS Interfaces. +version_added: 1.0.0 +author: Trishna Guha (@trishnaguha) +notes: +- Tested against NXOS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section ^interface). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dictionary of LACP interfaces options. + type: list + elements: dict + suboptions: + name: + description: + - Name of the interface. + required: true + type: str + port_priority: + description: + - LACP port priority for the interface. Range 1-65535. Applicable only for + Ethernet. + type: int + rate: + description: + - Rate at which PDUs are sent by LACP. Applicable only for Ethernet. At fast + rate LACP is transmitted once every 1 second. At normal rate LACP is transmitted + every 30 seconds after the link is bundled. + type: str + choices: + - fast + - normal + links: + description: + - This dict contains configurable options related to max and min port-channel + links. Applicable only for Port-channel. + type: dict + suboptions: + max: + description: + - Port-channel max bundle. + type: int + min: + description: + - Port-channel min links. + type: int + mode: + description: + - LACP mode. Applicable only for Port-channel. + type: str + choices: + - delay + suspend_individual: + description: + - port-channel lacp state. Disabling this will cause lacp to put the port + to individual state and not suspend the port in case it does not get LACP + BPDU from the peer ports in the port-channel. + type: bool + convergence: + description: + - This dict contains configurable options related to convergence. Applicable + only for Port-channel. + type: dict + suboptions: + graceful: + description: + - port-channel lacp graceful convergence. Disable this only with lacp + ports connected to Non-Nexus peer. Disabling this with Nexus peer can + lead to port suspension. + type: bool + vpc: + description: + - Enable lacp convergence for vPC port channels. + type: bool + state: + description: + - The state of the configuration after module completion. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# + +- name: Merge provided configuration with device configuration. + cisco.nxos.nxos_lacp_interfaces: + config: + - name: Ethernet1/3 + port_priority: 5 + rate: fast + state: merged + +# After state: +# ------------ +# +# interface Ethernet1/3 +# lacp port-priority 5 +# lacp rate fast + + +# Using replaced + +# Before state: +# ------------- +# +# interface Ethernet1/3 +# lacp port-priority 5 +# interface port-channel11 +# lacp mode delay + +- name: Replace device lacp interfaces configuration with the given configuration. + cisco.nxos.nxos_lacp_interfaces: + config: + - name: port-channel11 + links: + min: 4 + state: replaced + +# After state: +# ------------ +# +# interface Ethernet1/3 +# lacp port-priority 5 +# interface port-channel11 +# lacp min-links 4 + + +# Using overridden + +# Before state: +# ------------- +# +# interface Ethernet1/3 +# lacp port-priority 5 +# interface port-channel11 +# lacp mode delay + +- name: Override device configuration of all LACP interfaces attributes of given interfaces + on device with provided configuration. + cisco.nxos.nxos_lacp_interfaces: + config: + - name: port-channel11 + links: + min: 4 + state: overridden + +# After state: +# ------------ +# +# interface port-channel11 +# lacp min-links 4 + + +# Using deleted + +# Before state: +# ------------- +# +# interface Ethernet1/3 +# lacp port-priority 5 +# interface port-channel11 +# lacp mode delay + +- name: Delete LACP interfaces configurations. + cisco.nxos.nxos_lacp_interfaces: + state: deleted + +# After state: +# ------------ +# + +# Using rendered + +- name: Use rendered state to convert task input to device specific commands + cisco.nxos.nxos_lacp_interfaces: + config: + - name: Ethernet1/800 + rate: fast + - name: Ethernet1/801 + rate: fast + port_priority: 32 + - name: port-channel10 + links: + max: 15 + min: 2 + convergence: + graceful: true + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "interface Ethernet1/800" +# - "lacp rate fast" +# - "interface Ethernet1/801" +# - "lacp port-priority 32" +# - "lacp rate fast" +# - "interface port-channel10" +# - "lacp min-links 2" +# - "lacp max-bundle 15" +# - "lacp graceful-convergence" + +# Using parsed + +# parsed.cfg +# ------------ + +# interface port-channel10 +# lacp min-links 10 +# lacp max-bundle 15 +# interface Ethernet1/800 +# lacp port-priority 100 +# lacp rate fast + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_lacp_interfaces: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- + +# parsed: +# - name: port-channel10 +# links: +# max: 15 +# min: 10 +# - name: Ethernet1/800 +# port_priority: 100 +# rate: fast + +# Using gathered + +# Existing device config state +# ------------------------------- +# interface Ethernet1/1 +# lacp port-priority 5 +# lacp rate fast +# interface port-channel10 +# lacp mode delay +# interface port-channel11 +# lacp max-bundle 10 +# lacp min-links 5 + +- name: Gather lacp_interfaces facts from the device using nxos_lacp_interfaces + cisco.nxos.nxos_lacp_interfaces: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# - name: Ethernet1/1 +# port_priority: 5 +# rate: fast +# - name: port-channel10 +# mode: delay +# - name: port-channel11 +# links: +# max: 10 +# min: 5 +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: ['interface port-channel10', 'lacp min-links 5', 'lacp mode delay'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp_interfaces.lacp_interfaces import ( + Lacp_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lacp_interfaces.lacp_interfaces import ( + Lacp_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=Lacp_interfacesArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = Lacp_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py new file mode 100644 index 00000000..ce06462e --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py @@ -0,0 +1,368 @@ +#!/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 nxos_lag_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_lag_interfaces +short_description: LAG interfaces resource module +description: This module manages attributes of link aggregation groups of NX-OS Interfaces. +version_added: 1.0.0 +author: +- Trishna Guha (@trishnaguha) +- Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section ^interface). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of link aggregation group configurations. + type: list + elements: dict + suboptions: + name: + description: + - Name of the link aggregation group (LAG). + type: str + required: true + members: + 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: + - Link aggregation group (LAG). + type: str + choices: + - 'active' + - 'on' + - 'passive' + force: + description: + - When true it forces link aggregation group members to match what is + declared in the members param. This can be used to remove members. + type: bool + state: + description: + - The state of the configuration after module completion. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged +notes: +- Tested against NXOS 7.3.(0)D1(1) on VIRL. +- Unsupported for Cisco MDS +- This module works with connection C(network_cli). + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# +# interface Ethernet1/4 + +- name: Merge provided configuration with device configuration. + cisco.nxos.nxos_lag_interfaces: + config: + - name: port-channel99 + members: + - member: Ethernet1/4 + state: merged + +# After state: +# ------------ +# +# interface Ethernet1/4 +# channel-group 99 + + +# Using replaced + +# Before state: +# ------------- +# +# interface Ethernet1/4 +# channel-group 99 mode active + +- name: Replace device configuration of specified LAG attributes of given interfaces + with provided configuration. + cisco.nxos.nxos_lag_interfaces: + config: + - name: port-channel10 + members: + - member: Ethernet1/4 + state: replaced + +# After state: +# ------------ +# +# interface Ethernet1/4 +# channel-group 10 + + +# Using overridden + +# Before state: +# ------------- +# +# interface Ethernet1/4 +# channel-group 10 +# interface Ethernet1/2 +# channel-group 99 mode passive + +- name: Override device configuration of all LAG attributes of given interfaces on + device with provided configuration. + cisco.nxos.nxos_lag_interfaces: + config: + - name: port-channel20 + members: + - member: Ethernet1/6 + force: true + state: overridden + +# After state: +# ------------ +# interface Ethernet1/2 +# interface Ethernet1/4 +# interface Ethernet1/6 +# channel-group 20 force + + +# Using deleted + +# Before state: +# ------------- +# +# interface Ethernet1/4 +# channel-group 99 mode active + +- name: Delete LAG attributes of given interface (This won't delete the port-channel + itself). + cisco.nxos.nxos_lag_interfaces: + config: + - port-channel: port-channel99 + state: deleted + +- name: Delete LAG attributes of all the interfaces + cisco.nxos.nxos_lag_interfaces: + state: deleted + +# After state: +# ------------ +# +# interface Ethernet1/4 +# no channel-group 99 + +# Using rendered + +- name: Use rendered state to convert task input to device specific commands + cisco.nxos.nxos_lag_interfaces: + config: + - name: port-channel10 + members: + - member: Ethernet1/800 + mode: active + - member: Ethernet1/801 + - name: port-channel11 + members: + - member: Ethernet1/802 + mode: passive + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "interface Ethernet1/800" +# - "channel-group 10 mode active" +# - "interface Ethernet1/801" +# - "channel-group 10" +# - "interface Ethernet1/802" +# - "channel-group 11 mode passive" + +# Using parsed + +# parsed.cfg +# ------------ + +# interface port-channel10 +# interface port-channel11 +# interface port-channel12 +# interface Ethernet1/800 +# channel-group 10 mode active +# interface Ethernet1/801 +# channel-group 10 mode active +# interface Ethernet1/802 +# channel-group 11 mode passive +# interface Ethernet1/803 +# channel-group 11 mode passive + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_lag_interfaces: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- + +# parsed: +# - members: +# - member: Ethernet1/800 +# mode: active +# - member: Ethernet1/801 +# mode: active +# name: port-channel10 +# +# - members: +# - member: Ethernet1/802 +# mode: passive +# - member: Ethernet1/803 +# mode: passive +# name: port-channel11 +# +# - name: port-channel12 + +# Using gathered + +# Existing device config state +# ------------------------------- +# interface port-channel10 +# interface port-channel11 +# interface Ethernet1/1 +# channel-group 10 mode active +# interface Ethernet1/2 +# channel-group 11 mode passive +# + +- name: Gather lag_interfaces facts from the device using nxos_lag_interfaces + cisco.nxos.nxos_lag_interfaces: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# - name: port-channel10 +# members: +# - member: Ethernet1/1 +# mode: active +# - name: port-channel11 +# members: +# - member: Ethernet1/2 +# mode: passive +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: + - "interface Ethernet1/800" + - "channel-group 10 mode active" + - "interface Ethernet1/801" + - "channel-group 10" + - "interface Ethernet1/802" + - "channel-group 11 mode passive" +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lag_interfaces.lag_interfaces import ( + Lag_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lag_interfaces.lag_interfaces import ( + Lag_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=Lag_interfacesArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = Lag_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py new file mode 100644 index 00000000..3bd3c553 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py @@ -0,0 +1,345 @@ +#!/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 nxos_lldp_global +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_lldp_global +short_description: LLDP resource module +description: This module configures and manages the Link Layer Discovery Protocol(LLDP) + attributes on NX-OS platforms. +version_added: 1.0.0 +author: Adharsh Srivats Rangarajan (@adharshsrivatsr) +notes: +- Tested against NxOS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- The LLDP feature needs to be enabled before using this module +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | include lldp). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: + - A list of link layer discovery configurations + type: dict + suboptions: + holdtime: + description: + - Amount of time the receiving device should hold the information (in seconds) + type: int + port_id: + description: + - This attribute defines if the interface names should be advertised in the + long(0) or short(1) form. + type: int + choices: + - 0 + - 1 + reinit: + description: + - Amount of time to delay the initialization of LLDP on any interface (in + seconds) + type: int + timer: + description: + - Frequency at which LLDP updates need to be transmitted (in seconds) + type: int + tlv_select: + description: + - This attribute can be used to specify the TLVs that need to be sent and + received in the LLDP packets. By default, all TLVs are advertised + type: dict + suboptions: + dcbxp: + description: + - Used to specify the Data Center Bridging Exchange Protocol TLV + type: bool + management_address: + description: + - Used to specify the management address in TLV messages + type: dict + suboptions: + v4: + description: Management address with TLV v4 + type: bool + v6: + description: Management address with TLV v6 + type: bool + port: + description: + - Used to manage port based attributes in TLV messages + type: dict + suboptions: + description: + description: + - Used to specify the port description TLV + type: bool + vlan: + description: + - Used to specify the port VLAN ID TLV + type: bool + power_management: + description: + - Used to specify IEEE 802.3 DTE Power via MDI TLV + type: bool + system: + description: + - Used to manage system based attributes in TLV messages + type: dict + suboptions: + capabilities: + description: + - Used to specify the system capabilities TLV + type: bool + description: + description: + - Used to specify the system description TLV + type: bool + name: + description: + - Used to specify the system name TLV + type: bool + state: + description: + - The state of the configuration after module completion + type: str + choices: + - merged + - replaced + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged +# Before state: +# ------------- +# +# user(config)# show running-config | include lldp +# feature lldp + +- name: Merge provided configuration with device configuration + cisco.nxos.nxos_lldp_global: + config: + timer: 35 + holdtime: 100 + state: merged + +# After state: +# ------------ +# +# user(config)# show running-config | include lldp +# feature lldp +# lldp timer 35 +# lldp holdtime 100 + + +# Using replaced +# Before state: +# ------------- +# +# user(config)# show running-config | include lldp +# feature lldp +# lldp holdtime 100 +# lldp reinit 5 +# lldp timer 35 + +- name: Replace device configuration of specific LLDP attributes with provided configuration + cisco.nxos.nxos_lldp_global: + config: + timer: 40 + tlv_select: + system: + description: true + name: false + management_address: + v4: true + state: replaced + +# After state: +# ------------ +# +# user(config)# show running-config | include lldp +# feature lldp +# lldp timer 40 +# no lldp tlv-select system-name + + +# Using deleted +# Before state: +# ------------- +# +# user(config)# show running-config | include lldp +# feature lldp +# lldp holdtime 5 +# lldp reinit 3 + +- name: Delete LLDP configuration (this will by default remove all lldp configuration) + cisco.nxos.nxos_lldp_global: + state: deleted + +# After state: +# ------------ +# +# user(config)# show running-config | include lldp +# feature lldp + +# Using rendered + +- name: Use rendered state to convert task input to device specific commands + cisco.nxos.nxos_lldp_global: + config: + holdtime: 130 + port_id: 1 + reinit: 5 + tlv_select: + dcbxp: yes + power_management: yes + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - "lldp tlv-select dcbxp" +# - "lldp tlv-select power-management" +# - "lldp portid-subtype 1" +# - "lldp reinit 5" +# - "lldp holdtime 130" + +# Using parsed + +# parsed.cfg +# ------------ +# lldp holdtime 131 +# lldp reinit 7 +# no lldp tlv-select system-name +# no lldp tlv-select system-description + +# Task output (redacted) +# ----------------------- + +# parsed: +# holdtime: 131 +# reinit: 7 +# tlv_select: +# system: +# description: false +# name: false + +# Using gathered + +# Existing device config state +# ------------------------------- +# feature lldp +# lldp holdtime 129 +# lldp reinit 5 +# lldp timer 35 +# no lldp tlv-select system-name + +# Task output (redacted) +# ----------------------- + +# gathered: +# reinit: 5 +# timer: 35 +# tlv_select: +# system: +# name: False +# holdtime: 129 +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['lldp holdtime 125', 'lldp reinit 4', 'no lldp tlv-select system-name'] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_global.lldp_global import ( + Lldp_globalArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lldp_global.lldp_global import ( + Lldp_global, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=Lldp_globalArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = Lldp_global(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py new file mode 100644 index 00000000..cf49a9ac --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py @@ -0,0 +1,263 @@ +#!/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 nxos_lldp_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_lldp_interfaces +short_description: LLDP interfaces resource module +description: This module manages interfaces' configuration for Link Layer Discovery + Protocol (LLDP) on NX-OS platforms. +version_added: 1.0.0 +author: Adharsh Srivats Rangarajan (@adharshsrivatsr) +notes: +- Tested against NXOS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- The LLDP feature needs to be enabled before using this module +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section ^interface). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: + - A list of link layer discovery configurations for interfaces. + type: list + elements: dict + suboptions: + name: + description: + - Name of the interface + required: true + type: str + receive: + description: + - Used to enable or disable the reception of LLDP packets on that interface. + By default, this is enabled after LLDP is enabled globally. + type: bool + transmit: + description: + - Used to enable or disable the transmission of LLDP packets on that interface. + By default, this is enabled after LLDP is enabled globally. + type: bool + tlv_set: + description: + - Used to configure TLV parameters on the interface + type: dict + suboptions: + management_address: + description: + - Used to mention the IPv4 or IPv6 management address for the interface + type: str + vlan: + description: + - Used to mention the VLAN for the interface + type: int + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# + +- name: Merge provided configuration with device configuration + cisco.nxos.nxos_lldp_interfaces: + config: + - name: Ethernet1/4 + receive: false + transmit: true + tlv_set: + management_address: 192.168.122.64 + vlan: 12 + state: merged + +# After state: +# ------------- +# +# interface Ethernet1/4 +# no lldp receive +# lldp tlv-set management-address 192.168.122.64 +# lldp tlv-set vlan 12 + + +# Using replaced + +# Before state: +# ------------ +# +# interface Ethernet1/4 +# no lldp receive +# lldp tlv-set management-address 192.168.122.64 +# interface Ethernet1/5 +# no lldp transmit +# lldp tlv-set vlan 10 + +- name: Replace LLDP configuration on interfaces with given configuration + cisco.nxos.nxos_lldp_interfaces: + config: + - name: Ethernet1/4 + transmit: no + tlv_set: + vlan: 2 + state: replaced + + +# After state: +# ----------- +# +# interface Ethernet1/4 +# no lldp transmit +# lldp tlv_set vlan 2 +# interface Ethernet1/5 +# no lldp transmit +# lldp tlv-set vlan 10 + + +# Using overridden + +# Before state: +# ------------ +# +# interface Ethernet1/4 +# no lldp receive +# lldp tlv-set management-address 192.168.122.64 +# interface Ethernet1/5 +# no lldp transmit +# lldp tlv-set vlan 10 + +- name: Override LLDP configuration on all interfaces with given configuration + cisco.nxos.nxos_lldp_interfaces: + config: + - name: Ethernet1/7 + receive: no + tlv_set: + vlan: 12 + state: overridden + + +# After state: +# ----------- +# +# interface Ethernet1/7 +# no lldp receive +# lldp tlv_set vlan 12 + + +# Using deleted + +# Before state: +# ------------ +# +# interface Ethernet1/4 +# lldp tlv-set management vlan 24 +# no lldp transmit +# interface mgmt0 +# no lldp receive + +- name: Delete LLDP interfaces configuration + cisco.nxos.nxos_lldp_interfaces: + state: deleted + +# After state: +# ------------ +# + + +""" +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: ['interface Ethernet1/2', 'lldp receive', 'lldp tlv-set vlan 12'] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_interfaces.lldp_interfaces import ( + Lldp_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lldp_interfaces.lldp_interfaces import ( + Lldp_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Lldp_interfacesArgs.argument_spec, + supports_check_mode=True, + ) + + result = Lldp_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py new file mode 100644 index 00000000..7782eb32 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py @@ -0,0 +1,940 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +# Copyright: (c) 2017, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = """ +module: nxos_logging +author: Trishna Guha (@trishnaguha) +short_description: Manage logging on network devices +notes: +- Limited Support for Cisco MDS +description: +- This module provides declarative management of logging on Cisco NX-OS devices. +version_added: 1.0.0 +deprecated: + alternative: nxos_logging_global + why: Updated module released with more functionality. + removed_at_date: '2023-08-01' +options: + dest: + description: + - Destination of the logs. + choices: + - console + - logfile + - module + - monitor + - server + type: str + remote_server: + description: + - Hostname or IP Address for remote logging (when dest is 'server'). + type: str + use_vrf: + description: + - VRF to be used while configuring remote logging (when dest is 'server'). + type: str + interface: + description: + - Interface to be used while configuring source-interface for logging (e.g., 'Ethernet1/2', + 'mgmt0') + type: str + name: + description: + - If value of C(dest) is I(logfile) it indicates file-name. + type: str + facility: + description: + - Facility name for logging. + type: str + dest_level: + description: + - Set logging severity levels. + aliases: + - level + type: int + facility_level: + description: + - Set logging severity levels for facility based log messages. + type: int + aggregate: + description: List of logging definitions. + type: list + elements: dict + state: + description: + - State of the logging configuration. + default: present + choices: + - present + - absent + type: str + event: + description: + - Link/trunk enable/default interface configuration logging + choices: + - link-enable + - link-default + - trunk-enable + - trunk-default + type: str + interface_message: + description: + - Add interface description to interface syslogs. Does not work with version 6.0 + images using nxapi as a transport. + choices: + - add-interface-description + type: str + file_size: + description: + - Set logfile size + type: int + facility_link_status: + description: + - Set logging facility ethpm link status. Not idempotent with version 6.0 images. + choices: + - link-down-notif + - link-down-error + - link-up-notif + - link-up-error + type: str + timestamp: + description: + - Set logging timestamp format + choices: + - microseconds + - milliseconds + - seconds + type: str + purge: + description: + - Remove any switch logging configuration that does not match what has been configured + Not supported for ansible_connection local. All nxos_logging tasks must use + the same ansible_connection type. + type: bool + default: false +extends_documentation_fragment: +- cisco.nxos.nxos +""" + +EXAMPLES = """ +- name: configure console logging with level + cisco.nxos.nxos_logging: + dest: console + level: 2 + state: present +- name: remove console logging configuration + cisco.nxos.nxos_logging: + dest: console + level: 2 + state: absent +- name: configure file logging with level + cisco.nxos.nxos_logging: + dest: logfile + name: testfile + dest_level: 3 + state: present +- name: Configure logging logfile with size + cisco.nxos.nxos_logging: + dest: logfile + name: testfile + dest_level: 3 + file_size: 16384 +- name: configure facility level logging + cisco.nxos.nxos_logging: + facility: daemon + facility_level: 0 + state: present +- name: remove facility level logging + cisco.nxos.nxos_logging: + facility: daemon + facility_level: 0 + state: absent +- name: Configure Remote Logging + cisco.nxos.nxos_logging: + dest: server + remote_server: test-syslogserver.com + facility: auth + facility_level: 1 + use_vrf: management + state: present +- name: Configure Source Interface for Logging + cisco.nxos.nxos_logging: + interface: mgmt0 + state: present +- name: Purge nxos_logging configuration not managed by this playbook + cisco.nxos.nxos_logging: + purge: true +- name: Configure logging timestamp + cisco.nxos.nxos_logging: + timestamp: milliseconds + state: present +- name: Configure logging facility ethpm link status + cisco.nxos.nxos_logging: + facility: ethpm + facility_link_status: link-up-notif + state: present +- name: Configure logging message ethernet description + cisco.nxos.nxos_logging: + interface_message: add-interface-description + state: present +- name: Configure logging event link enable + cisco.nxos.nxos_logging: + event: link-enable + state: present +- name: Configure logging using aggregate + cisco.nxos.nxos_logging: + aggregate: + - {dest: console, dest_level: 2} + - {dest: logfile, dest_level: 2, name: testfile} + - {facility: daemon, facility_level: 0} + state: present +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - logging console 2 + - logging logfile testfile 3 + - logging level daemon 0 +""" + +import copy +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, + normalize_interface, + read_module_context, + run_commands, + save_module_context, +) + + +STATIC_CLI = { + "link-enable": "logging event link-status enable", + "link-default": "logging event link-status default", + "trunk-enable": "logging event trunk-status enable", + "trunk-default": "logging event trunk-status default", + "microseconds": "logging timestamp microseconds", + "milliseconds": "logging timestamp milliseconds", + "seconds": "logging timestamp seconds", + "link-up-error": "link-up error", + "link-up-notif": "link-up notif", + "link-down-error": "link-down error", + "link-down-notif": "link-down notif", + "add-interface-description": "logging message interface type ethernet description", +} + +DEFAULT_LOGGING_LEVEL = { + 0: [], + 1: [], + 2: ["pktmgr"], + 3: ["adjmgr", "arp", "icmpv6", "l2rib", "netstack"], + 4: [], + 5: ["mrib", "m6rib"], + 6: [], + 7: [], +} + +DEST_GROUP = ["console", "logfile", "module", "monitor", "server"] + + +def map_obj_to_commands(module, updates): + commands = list() + want, have = updates + + for w in want: + state = w["state"] + del w["state"] + + if state == "absent" and w in have: + if w["facility"] is not None: + if ( + not w["dest"] + and not w["facility_link_status"] + and w["facility"] not in DEFAULT_LOGGING_LEVEL[int(w["facility_level"])] + ): + commands.append( + "no logging level {0} {1}".format(w["facility"], w["facility_level"]), + ) + + if w["facility_link_status"] and w["facility"] in ("ethpm"): + commands.append( + "no logging level {0} {1}".format( + w["facility"], + STATIC_CLI[w["facility_link_status"]], + ), + ) + + if w["name"] is not None: + commands.append("no logging logfile") + + if w["dest"] in ("console", "module", "monitor"): + commands.append("no logging {0}".format(w["dest"])) + + if w["dest"] == "server": + commands.append("no logging server {0}".format(w["remote_server"])) + + if w["interface"]: + commands.append("no logging source-interface") + + if w["event"] and w["event"] in STATIC_CLI: + commands.append("no " + STATIC_CLI[w["event"]]) + + if w["message"] and w["message"] in STATIC_CLI: + commands.append("no " + STATIC_CLI[w["message"]]) + + if w["timestamp"] and w["timestamp"] in STATIC_CLI: + commands.append("no " + STATIC_CLI[w["timestamp"]]) + + if state == "present" and w not in have: + if w["facility"] is None: + if w["dest"]: + if w["dest"] not in ("logfile", "server"): + commands.append("logging {0} {1}".format(w["dest"], w["dest_level"])) + + elif w["dest"] == "logfile": + if w["file_size"]: + commands.append( + "logging logfile {0} {1} size {2}".format( + w["name"], + w["dest_level"], + w["file_size"], + ), + ) + else: + commands.append( + "logging logfile {0} {1}".format(w["name"], w["dest_level"]), + ) + + elif w["dest"] == "server": + if w["facility_level"]: + if w["use_vrf"]: + commands.append( + "logging server {0} {1} use-vrf {2}".format( + w["remote_server"], + w["facility_level"], + w["use_vrf"], + ), + ) + else: + commands.append( + "logging server {0} {1}".format( + w["remote_server"], + w["facility_level"], + ), + ) + + else: + if w["use_vrf"]: + commands.append( + "logging server {0} use-vrf {1}".format( + w["remote_server"], + w["use_vrf"], + ), + ) + else: + commands.append("logging server {0}".format(w["remote_server"])) + + if w["facility"]: + if w["dest"] == "server": + if w["facility_level"]: + if w["use_vrf"]: + commands.append( + "logging server {0} {1} facility {2} use-vrf {3}".format( + w["remote_server"], + w["facility_level"], + w["facility"], + w["use_vrf"], + ), + ) + else: + commands.append( + "logging server {0} {1} facility {2}".format( + w["remote_server"], + w["facility_level"], + w["facility"], + ), + ) + else: + if w["use_vrf"]: + commands.append( + "logging server {0} facility {1} use-vrf {2}".format( + w["remote_server"], + w["facility"], + w["use_vrf"], + ), + ) + else: + commands.append( + "logging server {0} facility {1}".format( + w["remote_server"], + w["facility"], + ), + ) + else: + if w["facility_link_status"]: + commands.append( + "logging level {0} {1}".format( + w["facility"], + STATIC_CLI[w["facility_link_status"]], + ), + ) + else: + if not match_facility_default(module, w["facility"], w["facility_level"]): + commands.append( + "logging level {0} {1}".format(w["facility"], w["facility_level"]), + ) + + if w["interface"]: + commands.append( + "logging source-interface {0} {1}".format(*split_interface(w["interface"])), + ) + + if w["event"] and w["event"] in STATIC_CLI: + commands.append(STATIC_CLI[w["event"]]) + + if w["message"] and w["message"] in STATIC_CLI: + commands.append(STATIC_CLI[w["message"]]) + + if w["timestamp"] and w["timestamp"] in STATIC_CLI: + commands.append(STATIC_CLI[w["timestamp"]]) + + return commands + + +def match_facility_default(module, facility, want_level): + """Check wanted facility to see if it matches current device default""" + + matches_default = False + # Sample output from show logging level command + # Facility Default Severity Current Session Severity + # -------- ---------------- ------------------------ + # bfd 5 5 + # + # 0(emergencies) 1(alerts) 2(critical) + # 3(errors) 4(warnings) 5(notifications) + # 6(information) 7(debugging) + + regexl = r"\S+\s+(\d+)\s+(\d+)" + cmd = { + "command": "show logging level {0}".format(facility), + "output": "text", + } + facility_data = run_commands(module, cmd) + for line in facility_data[0].split("\n"): + mo = re.search(regexl, line) + if mo and int(mo.group(1)) == int(want_level) and int(mo.group(2)) == int(want_level): + matches_default = True + + return matches_default + + +def split_interface(interface): + match = re.search(r"(\D+)(\S*)", interface, re.M) + if match: + return match.group(1), match.group(2) + + +def parse_facility_link_status(line, facility, status): + facility_link_status = None + + if facility is not None: + match = re.search(r"logging level {0} {1} (\S+)".format(facility, status), line, re.M) + if match: + facility_link_status = status + "-" + match.group(1) + + return facility_link_status + + +def parse_event_status(line, event): + status = None + + match = re.search(r"logging event {0} (\S+)".format(event + "-status"), line, re.M) + if match: + state = match.group(1) + if state: + status = state + + return status + + +def parse_event(line): + event = None + + match = re.search(r"logging event (\S+)", line, re.M) + if match: + state = match.group(1) + if state == "link-status": + event = "link" + elif state == "trunk-status": + event = "trunk" + + return event + + +def parse_message(line): + message = None + + match = re.search(r"logging message interface type ethernet description", line, re.M) + if match: + message = "add-interface-description" + + return message + + +def parse_file_size(line, name, level): + file_size = None + + match = re.search(r"logging logfile {0} {1} size (\S+)".format(name, level), line, re.M) + if match: + file_size = match.group(1) + if file_size == "8192" or file_size == "4194304": + file_size = None + + return file_size + + +def parse_timestamp(line): + timestamp = None + + match = re.search(r"logging timestamp (\S+)", line, re.M) + if match: + timestamp = match.group(1) + + return timestamp + + +def parse_name(line, dest): + name = None + + if dest is not None: + if dest == "logfile": + match = re.search(r"logging logfile (\S+)", line, re.M) + if match: + name = match.group(1) + else: + pass + + return name + + +def parse_remote_server(line, dest): + remote_server = None + + if dest and dest == "server": + match = re.search(r"logging server (\S+)", line, re.M) + if match: + remote_server = match.group(1) + + return remote_server + + +def parse_dest_level(line, dest, name): + dest_level = None + + def parse_match(match): + level = None + if match: + if int(match.group(1)) in range(0, 8): + level = match.group(1) + else: + pass + return level + + if dest and dest != "server": + if dest == "logfile": + match = re.search(r"logging logfile {0} (\S+)".format(name), line, re.M) + if match: + dest_level = parse_match(match) + + elif dest == "server": + match = re.search(r"logging server (?:\S+) (\d+)", line, re.M) + if match: + dest_level = parse_match(match) + else: + match = re.search(r"logging {0} (\S+)".format(dest), line, re.M) + if match: + dest_level = parse_match(match) + + return dest_level + + +def parse_facility_level(line, facility, dest): + facility_level = None + + if dest == "server": + match = re.search(r"logging server (?:\S+) (\d+)", line, re.M) + if match: + facility_level = match.group(1) + + elif facility is not None: + match = re.search(r"logging level {0} (\S+)".format(facility), line, re.M) + if match: + facility_level = match.group(1) + + return facility_level + + +def parse_facility(line): + facility = None + + match = re.search( + r"logging server (?:\S+) (?:\d+) (?:\S+) (?:\S+) (?:\S+) (\S+)", + line, + re.M, + ) + if match: + facility = match.group(1) + + return facility + + +def parse_use_vrf(line, dest): + use_vrf = None + + if dest and dest == "server": + match = re.search(r"logging server (?:\S+) (?:\d+) use-vrf (\S+)", line, re.M) + if match: + use_vrf = match.group(1) + + return use_vrf + + +def parse_interface(line): + interface = None + + match = re.search(r"logging source-interface (\S*)", line, re.M) + if match: + interface = match.group(1) + + return interface + + +def map_config_to_obj(module): + obj = [] + + data = get_config(module, flags=[" all | section logging"]) + + for line in data.split("\n"): + if re.search(r"no (\S+)", line, re.M): + state = "absent" + else: + state = "present" + + match = re.search(r"logging (\S+)", line, re.M) + if state == "present" and match: + event_status = None + name = None + dest_level = None + dest = None + facility = None + remote_server = None + facility_link_status = None + file_size = None + facility_level = None + + if match.group(1) in DEST_GROUP: + dest = match.group(1) + + name = parse_name(line, dest) + remote_server = parse_remote_server(line, dest) + dest_level = parse_dest_level(line, dest, name) + + if dest == "server": + facility = parse_facility(line) + + facility_level = parse_facility_level(line, facility, dest) + + if dest == "logfile": + file_size = parse_file_size(line, name, dest_level) + + elif match.group(1) == "level": + match_facility = re.search(r"logging level (\S+)", line, re.M) + facility = match_facility.group(1) + + level = parse_facility_level(line, facility, dest) + if level.isdigit(): + facility_level = level + else: + facility_link_status = parse_facility_link_status(line, facility, level) + + elif match.group(1) == "event" and state == "present": + event = parse_event(line) + if event: + status = parse_event_status(line, event) + if status: + event_status = event + "-" + status + else: + continue + + else: + pass + + obj.append( + { + "dest": dest, + "remote_server": remote_server, + "use_vrf": parse_use_vrf(line, dest), + "name": name, + "facility": facility, + "dest_level": dest_level, + "facility_level": facility_level, + "interface": parse_interface(line), + "facility_link_status": facility_link_status, + "event": event_status, + "file_size": file_size, + "message": parse_message(line), + "timestamp": parse_timestamp(line), + }, + ) + + cmd = [ + { + "command": "show logging | section enabled | section console", + "output": "text", + }, + { + "command": "show logging | section enabled | section monitor", + "output": "text", + }, + ] + + default_data = run_commands(module, cmd) + + for line in default_data: + flag = False + match = re.search( + r"Logging (\w+):(?:\s+) (?:\w+) (?:\W)Severity: (\w+)", + str(line), + re.M, + ) + if match: + if match.group(1) == "console" and match.group(2) == "critical": + dest_level = "2" + flag = True + elif match.group(1) == "monitor" and match.group(2) == "notifications": + dest_level = "5" + flag = True + if flag: + obj.append( + { + "dest": match.group(1), + "remote_server": None, + "name": None, + "facility": None, + "dest_level": dest_level, + "facility_level": None, + "use_vrf": None, + "interface": None, + "facility_link_status": None, + "event": None, + "file_size": None, + "message": None, + "timestamp": None, + }, + ) + + return obj + + +def map_params_to_obj(module): + obj = [] + + if "aggregate" in module.params and module.params["aggregate"]: + args = { + "dest": "", + "remote_server": "", + "use_vrf": "", + "name": "", + "facility": "", + "dest_level": "", + "facility_level": "", + "interface": "", + "facility_link_status": None, + "event": None, + "file_size": None, + "message": None, + "timestamp": None, + } + + for c in module.params["aggregate"]: + d = c.copy() + + for key in args: + if key not in d: + d[key] = None + + if d["dest_level"] is not None: + d["dest_level"] = str(d["dest_level"]) + + if d["facility_level"] is not None: + d["facility_level"] = str(d["facility_level"]) + + if d["interface"]: + d["interface"] = normalize_interface(d["interface"]) + + if "state" not in d: + d["state"] = module.params["state"] + + if d["file_size"]: + d["file_size"] = str(d["file_size"]) + + obj.append(d) + + else: + dest_level = None + facility_level = None + file_size = None + + if module.params["dest_level"] is not None: + dest_level = str(module.params["dest_level"]) + + if module.params["facility_level"] is not None: + facility_level = str(module.params["facility_level"]) + + if module.params["file_size"] is not None: + file_size = str(module.params["file_size"]) + + obj.append( + { + "dest": module.params["dest"], + "remote_server": module.params["remote_server"], + "use_vrf": module.params["use_vrf"], + "name": module.params["name"], + "facility": module.params["facility"], + "dest_level": dest_level, + "facility_level": facility_level, + "interface": normalize_interface(module.params["interface"]), + "state": module.params["state"], + "facility_link_status": module.params["facility_link_status"], + "event": module.params["event"], + "message": module.params["interface_message"], + "file_size": file_size, + "timestamp": module.params["timestamp"], + }, + ) + return obj + + +def merge_wants(wants, want): + if not wants: + wants = list() + + for w in want: + w = copy.copy(w) + state = w["state"] + del w["state"] + + if state == "absent": + if w in wants: + wants.remove(w) + elif w not in wants: + wants.append(w) + + return wants + + +def absent(h): + h["state"] = "absent" + return h + + +def outliers(haves, wants): + wants = list(wants) + return [absent(h) for h in haves if not (h in wants or wants.append(h))] + + +def main(): + """main entry point for module execution""" + argument_spec = dict( + dest=dict(choices=DEST_GROUP), + name=dict(), + facility=dict(), + remote_server=dict(), + use_vrf=dict(), + dest_level=dict(type="int", aliases=["level"]), + facility_level=dict(type="int"), + interface=dict(), + facility_link_status=dict( + choices=[ + "link-down-notif", + "link-down-error", + "link-up-notif", + "link-up-error", + ], + ), + event=dict( + choices=[ + "link-enable", + "link-default", + "trunk-enable", + "trunk-default", + ], + ), + interface_message=dict(choices=["add-interface-description"]), + file_size=dict(type="int"), + timestamp=dict(choices=["microseconds", "milliseconds", "seconds"]), + state=dict(default="present", choices=["present", "absent"]), + aggregate=dict(type="list", elements="dict"), + purge=dict(default=False, type="bool"), + ) + + required_if = [ + ("dest", "logfile", ["name"]), + ("dest", "server", ["remote_server"]), + ] + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True, + ) + + warnings = list() + + result = {"changed": False} + if warnings: + result["warnings"] = warnings + + want = map_params_to_obj(module) + merged_wants = merge_wants(read_module_context(module), want) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(module, (want, have)) + result["commands"] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result["changed"] = True + + save_module_context(module, merged_wants) + + if module.params.get("purge"): + pcommands = map_obj_to_commands(module, (outliers(have, merged_wants), have)) + if pcommands: + if not module.check_mode: + load_config(module, pcommands) + result["changed"] = True + result["commands"] += pcommands + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py new file mode 100644 index 00000000..1c060b01 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py @@ -0,0 +1,735 @@ +#!/usr/bin/python +# -*- 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 module file for nxos_logging_global +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_logging_global +short_description: Logging resource module. +description: +- This module manages logging configuration on devices running Cisco NX-OS. +version_added: 2.5.0 +notes: +- Tested against NX-OS 9.3.6 on Cisco Nexus Switches. +- Limited Support for Cisco MDS +- This module works with connection C(network_cli) and C(httpapi). +- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | include logging). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dictionary of logging configuration. + type: dict + suboptions: + console: + description: Set console logging parameters. + type: dict + suboptions: + state: + description: Enable or disable monitor logging. + type: str + choices: ["enabled", "disabled"] + severity: &sev + description: Set severity severity for console. + type: str + choices: + - emergency + - alert + - critical + - error + - warning + - notification + - informational + - debugging + event: + description: Interface events. + type: dict + suboptions: + link_status: + description: UPDOWN and CHANGE messages. + type: dict + suboptions: &event + enable: + description: To enable logging overriding port severity configuration. + type: bool + default: + description: Default logging configuration used by interfaces not explicitly configured. + type: bool + trunk_status: + description: TRUNK status messages. + type: dict + suboptions: *event + history: + description: Modifies severity severity or size for history table. + type: dict + suboptions: + severity: *sev + size: + description: Set history table size. + type: int + ip: + description: + - IP configuration. + - This option is unsupported on MDS switches. + type: dict + suboptions: + access_list: + description: Access-List. + type: dict + suboptions: + cache: + description: Set caching settings. + type: dict + suboptions: + entries: + description: Maximum number of log entries cached in software. + type: int + interval: + description: Log-update interval (in sec). + type: int + threshold: + description: Log-update threshold (number of hits) + type: int + detailed: + description: Detailed ACL information. + type: bool + include: + description: Include additional fields in syslogs. + type: dict + suboptions: + sgt: + description: Include source group tag info in syslogs. + type: bool + facilities: + description: Facility parameter for syslog messages. + type: list + elements: dict + suboptions: + facility: + description: Facility name. + type: str + severity: *sev + logfile: + description: Set file logging. + type: dict + suboptions: + state: + description: Enable or disable logfile. + type: str + choices: ["enabled", "disabled"] + name: + description: Logfile name. + type: str + severity: *sev + persistent_threshold: + description: + - Set persistent logging utilization alert threshold in percentage. + - This option is unsupported on MDS switches. + type: int + size: + description: Enter the logfile size in bytes. + type: int + module: + description: Set module(linecard) logging. + type: dict + suboptions: + state: + description: Enable or disable module logging. + type: str + choices: ["enabled", "disabled"] + severity: *sev + monitor: + description: Set terminal line(monitor) logging severity. + type: dict + suboptions: + state: + description: Enable or disable monitor logging. + type: str + choices: ["enabled", "disabled"] + severity: *sev + origin_id: + description: Enable origin information for Remote Syslog Server. + type: dict + suboptions: + hostname: + description: + - Use hostname as origin-id of logging messages. + - This option is mutually exclusive with I(ip) and I(string). + type: bool + ip: + description: + - Use ip address as origin-id of logging messages. + - This option is mutually exclusive with I(hostname) and I(string). + type: str + string: + description: + - Use text string as origin-id of logging messages. + - This option is mutually exclusive with I(hostname) and I(ip). + type: str + rate_limit: + description: Enable or disable rate limit for log messages. + type: str + choices: ["enabled", "disabled"] + rfc_strict: + description: + - Set RFC to which messages should compliant. + - Syslogs will be compliant to RFC 5424. + - This option is unsupported on MDS switches. + type: bool + hosts: + description: Enable forwarding to Remote Syslog Servers. + type: list + elements: dict + suboptions: + host: + description: Hostname/IPv4/IPv6 address of the Remote Syslog Server. + type: str + severity: *sev + facility: + description: Facility to use when forwarding to server. + type: str + port: + description: Destination Port when forwarding to remote server. + type: int + secure: + description: Enable secure connection to remote server. + type: dict + suboptions: + trustpoint: + description: Trustpoint configuration. + type: dict + suboptions: + client_identity: + description: + - Client Identity certificate for mutual authentication. + - Trustpoint to use for client certificate authentication. + type: str + use_vrf: + description: + - Display per-VRF information. + - This option is unsupported on MDS switches. + type: str + source_interface: + description: + - Enable Source-Interface for Remote Syslog Server. + - This option is unsupported on MDS switches. + type: str + timestamp: + description: Set logging timestamp granularity. + type: str + choices: ["microseconds", "milliseconds", "seconds"] + state: + description: + - The state the configuration should be left in. + - The states I(replaced) and I(overridden) have identical + behaviour for this module. + - Refer to examples for more details. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# nxos-9k-rdo# show running-config | include logging +# nxos-9k-rdo# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_logging_global: + config: + console: + severity: error + monitor: + severity: warning + ip: + access_list: + cache: + entries: 16384 + interval: 200 + threshold: 5000 + facilities: + - facility: auth + severity: critical + - facility: ospfv3 + severity: alert + - facility: ftp + severity: informational + hosts: + - host: 203.0.113.100 + severity: alert + use_vrf: management + - host: 203.0.113.101 + severity: error + facility: local6 + use_vrf: default + origin_id: + hostname: True + +# Task output +# ------------- +# before: {} +# +# commands: +# - "logging console 3" +# - "logging monitor 4" +# - "logging ip access-list cache entries 16384" +# - "logging ip access-list cache interval 200" +# - "logging ip access-list cache threshold 5000" +# - "logging severity auth 2" +# - "logging severity ospfv3 1" +# - "logging severity ftp 6" +# - "logging server 203.0.113.100 1 use-vrf management" +# - "logging server 203.0.113.101 3 facility local6 use-vrf default" +# - "logging origin-id hostname" +# +# after: +# console: +# severity: error +# facilities: +# - facility: auth +# severity: critical +# - facility: ftp +# severity: informational +# - facility: ospfv3 +# severity: alert +# ip: +# access_list: +# cache: +# entries: 16384 +# interval: 200 +# threshold: 5000 +# monitor: +# severity: warning +# origin_id: +# hostname: true +# hosts: +# - severity: alert +# host: 203.0.113.100 +# use_vrf: management +# - facility: local6 +# severity: error +# host: 203.0.113.101 +# use_vrf: default + +# After state: +# ------------ +# nxos-9k-rdo# show running-config | include logging +# logging console 3 +# logging monitor 4 +# logging ip access-list cache entries 16384 +# logging ip access-list cache interval 200 +# logging ip access-list cache threshold 5000 +# logging severity auth 2 +# logging severity ospfv3 1 +# logging severity ftp 6 +# logging origin-id hostname +# logging server 203.0.113.100 1 use-vrf management +# logging server 203.0.113.101 3 use-vrf default facility local6 + +# Using replaced + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | include logging +# logging console 3 +# logging monitor 4 +# logging ip access-list cache entries 16384 +# logging ip access-list cache interval 200 +# logging ip access-list cache threshold 5000 +# logging severity auth 2 +# logging severity ospfv3 1 +# logging severity ftp 6 +# logging origin-id hostname +# logging server 203.0.113.100 1 use-vrf management +# logging server 203.0.113.101 3 use-vrf default facility local6 + +- name: Replace logging configurations with provided config + cisco.nxos.nxos_logging_global: + config: + monitor: + severity: warning + ip: + access_list: + cache: + entries: 4096 + facilities: + - facility: auth + severity: critical + - facility: ospfv3 + severity: alert + - facility: ftp + severity: informational + hosts: + - host: 203.0.113.101 + severity: error + facility: local6 + use_vrf: default + - host: 198.51.100.101 + severity: alert + port: 6538 + use_vrf: management + origin_id: + ip: 192.0.2.100 + state: replaced + +# Task output +# ------------- +# before: +# console: +# severity: error +# facilities: +# - facility: auth +# severity: critical +# - facility: ftp +# severity: informational +# - facility: ospfv3 +# severity: alert +# ip: +# access_list: +# cache: +# entries: 16384 +# interval: 200 +# threshold: 5000 +# monitor: +# severity: warning +# origin_id: +# hostname: true +# hosts: +# - severity: alert +# host: 203.0.113.100 +# use_vrf: management +# - facility: local6 +# severity: error +# host: 203.0.113.101 +# use_vrf: default +# +# commands: +# - "logging console" +# - "logging ip access-list cache entries 4096" +# - "no logging ip access-list cache interval 200" +# - "no logging ip access-list cache threshold 5000" +# - "no logging origin-id hostname" +# - "logging origin-id ip 192.0.2.100" +# - "logging server 198.51.100.101 1 port 6538 use-vrf management" +# - "no logging server 203.0.113.100 1 use-vrf management" +# +# after: +# facilities: +# - facility: auth +# severity: critical +# - facility: ftp +# severity: informational +# - facility: ospfv3 +# severity: alert +# ip: +# access_list: +# cache: +# entries: 4096 +# monitor: +# severity: warning +# origin_id: +# ip: 192.0.2.100 +# hosts: +# - severity: alert +# port: 6538 +# host: 198.51.100.101 +# use_vrf: management +# - facility: local6 +# severity: error +# host: 203.0.113.101 +# use_vrf: default +# +# After state: +# ------------ +# nxos-9k-rdo# show running-config | include logging +# logging monitor 4 +# logging ip access-list cache entries 4096 +# logging severity auth 2 +# logging severity ospfv3 1 +# logging severity ftp 6 +# logging origin-id ip 192.0.2.100 +# logging server 203.0.113.101 3 use-vrf default facility local6 +# logging server 198.51.100.101 1 port 6538 use-vrf management + +# Using deleted to delete all logging configurations + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | include logging +# logging console 3 +# logging monitor 4 +# logging ip access-list cache entries 16384 +# logging ip access-list cache interval 200 +# logging ip access-list cache threshold 5000 +# logging severity auth 2 +# logging severity ospfv3 1 +# logging severity ftp 6 +# logging origin-id hostname +# logging server 203.0.113.100 1 use-vrf management +# logging server 203.0.113.101 3 use-vrf default facility local6 + +- name: Delete all logging configuration + cisco.nxos.nxos_logging_global: + state: deleted + +# Task output +# ------------- +# before: +# console: +# severity: error +# facilities: +# - facility: auth +# severity: critical +# - facility: ftp +# severity: informational +# - facility: ospfv3 +# severity: alert +# ip: +# access_list: +# cache: +# entries: 16384 +# interval: 200 +# threshold: 5000 +# monitor: +# severity: warning +# origin_id: +# hostname: true +# hosts: +# - severity: alert +# host: 203.0.113.100 +# use_vrf: management +# - facility: local6 +# severity: error +# host: 203.0.113.101 +# use_vrf: default +# +# commands: +# - "logging console" +# - "logging monitor" +# - "no logging ip access-list cache entries 16384" +# - "no logging ip access-list cache interval 200" +# - "no logging ip access-list cache threshold 5000" +# - "no logging origin-id hostname" +# - "no logging severity auth 2" +# - "no logging severity ospfv3 1" +# - "no logging severity ftp 6" +# - "no logging server 203.0.113.100 1 use-vrf management" +# - "no logging server 203.0.113.101 3 facility local6 use-vrf default" +# +# after: {} + +# Using rendered + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_logging_global: + config: + console: + severity: error + monitor: + severity: warning + ip: + access_list: + cache: + entries: 16384 + interval: 200 + threshold: 5000 + facilities: + - facility: auth + severity: critical + - facility: ospfv3 + severity: alert + - facility: ftp + severity: informational + hosts: + - host: 203.0.113.100 + severity: alert + use_vrf: management + - host: 203.0.113.101 + severity: error + facility: local6 + use_vrf: default + origin_id: + hostname: True + +# Task Output (redacted) +# ----------------------- +# rendered: +# - "logging console 3" +# - "logging monitor 4" +# - "logging ip access-list cache entries 16384" +# - "logging ip access-list cache interval 200" +# - "logging ip access-list cache threshold 5000" +# - "logging severity auth 2" +# - "logging severity ospfv3 1" +# - "logging severity ftp 6" +# - "logging server 203.0.113.100 1 use-vrf management" +# - "logging server 203.0.113.101 3 facility local6 use-vrf default" +# - "logging origin-id hostname" + +# Using parsed + +# parsed.cfg +# ------------ +# logging console 3 +# logging monitor 4 +# logging ip access-list cache entries 16384 +# logging ip access-list cache interval 200 +# logging ip access-list cache threshold 5000 +# logging severity auth 2 +# logging severity ospfv3 1 +# logging severity ftp 6 +# logging origin-id hostname +# logging server 203.0.113.100 1 use-vrf management +# logging server 203.0.113.101 3 use-vrf default facility local6 + +- name: Parse externally provided logging configuration + cisco.nxos.nxos_logging_global: + running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# console: +# severity: error +# facilities: +# - facility: auth +# severity: critical +# - facility: ftp +# severity: informational +# - facility: ospfv3 +# severity: alert +# ip: +# access_list: +# cache: +# entries: 16384 +# interval: 200 +# threshold: 5000 +# monitor: +# severity: warning +# origin_id: +# hostname: true +# hosts: +# - severity: alert +# host: 203.0.113.100 +# use_vrf: management +# - facility: local6 +# severity: error +# host: 203.0.113.101 +# use_vrf: default +""" + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when state is I(merged), I(replaced), I(overridden), I(deleted) or I(purged) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: dict + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when state is I(merged), I(replaced), I(overridden), I(deleted) or I(purged) + type: list + sample: + - "logging console 3" + - "logging monitor 4" + - "logging ip access-list cache entries 16384" + - "logging ip access-list cache interval 200" + - "logging ip access-list cache threshold 5000" +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when state is I(rendered) + type: list + sample: + - "logging ip access-list cache entries 4096" + - "no logging ip access-list cache interval 200" + - "no logging ip access-list cache threshold 5000" + - "no logging origin-id hostname" + - "logging origin-id ip 192.0.2.100" + - "logging server 198.51.100.101 1 port 6538 use-vrf management" +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when state is I(gathered) + type: list + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when state is I(parsed) + type: list + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.logging_global.logging_global import ( + Logging_globalArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.logging_global.logging_global import ( + Logging_global, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Logging_globalArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Logging_global(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py new file mode 100644 index 00000000..046436d4 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py @@ -0,0 +1,446 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_ntp +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages core NTP configuration. +notes: +- Limited Support for Cisco MDS +description: +- Manages core NTP configuration. +version_added: 1.0.0 +deprecated: + alternative: nxos_ntp_global + why: Updated module released with more functionality. + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +options: + server: + description: + - Network address of NTP server. + type: str + peer: + description: + - Network address of NTP peer. + type: str + key_id: + description: + - Authentication key identifier to use with given NTP server or peer or keyword + 'default'. + type: str + prefer: + description: + - Makes given NTP server or peer the preferred NTP server or peer for the device. + choices: + - enabled + - disabled + type: str + vrf_name: + description: + - Makes the device communicate with the given NTP server or peer over a specific + VRF or keyword 'default'. + type: str + source_addr: + description: + - Local source address from which NTP messages are sent or keyword 'default'. + type: str + source_int: + description: + - Local source interface from which NTP messages are sent. Must be fully qualified + interface name or keyword 'default' + type: str + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# Set NTP Server with parameters +- cisco.nxos.nxos_ntp: + server: 1.2.3.4 + key_id: 32 + prefer: enabled + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +""" + +RETURN = """ +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"address": "192.0.2.2", "key_id": "48", + "peer_type": "server", "prefer": "enabled", + "source": "192.0.2.3", "source_type": "source"} +existing: + description: + - k/v pairs of existing ntp server/peer + returned: always + type: dict + sample: {"address": "192.0.2.2", "key_id": "32", + "peer_type": "server", "prefer": "enabled", + "source": "ethernet2/1", "source_type": "source-interface"} +end_state: + description: k/v pairs of ntp info after module execution + returned: always + type: dict + sample: {"address": "192.0.2.2", "key_id": "48", + "peer_type": "server", "prefer": "enabled", + "source": "192.0.2.3", "source_type": "source"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["ntp server 192.0.2.2 prefer key 48", + "no ntp source-interface ethernet2/1", "ntp source 192.0.2.3"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module, command_type="cli_show"): + if "show run" not in command: + output = "json" + else: + output = "text" + + commands = [{"command": command, "output": output}] + return run_commands(module, commands) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_ntp_source(module): + source_type = None + source = None + command = "show run | inc ntp.source" + output = execute_show_command(command, module, command_type="cli_show_ascii") + + if output: + try: + if "interface" in output[0]: + source_type = "source-interface" + else: + source_type = "source" + source = output[0].split()[2].lower() + except (AttributeError, IndexError): + source_type = None + source = None + + return source_type, source + + +def get_ntp_peer(module): + command = "show run | inc ntp.(server|peer)" + ntp_peer_list = [] + response = execute_show_command(command, module, command_type="cli_show_ascii") + + if response: + if isinstance(response, list): + ntp = response[0] + else: + ntp = response + if ntp: + ntp_regex = ( + r".*ntp\s(server\s(?P<address>\S+)|peer\s(?P<peer_address>\S+))" + r"\s*((?P<prefer>prefer)\s*)?(use-vrf\s(?P<vrf_name>\S+)\s*)?" + r"(key\s(?P<key_id>\d+))?.*" + ) + + split_ntp = ntp.splitlines() + for peer_line in split_ntp: + if "access-group" in peer_line: + continue + ntp_peer = {} + try: + peer_address = None + vrf_name = "default" + prefer = None + key_id = None + match_ntp = re.match(ntp_regex, peer_line, re.DOTALL) + group_ntp = match_ntp.groupdict() + + address = group_ntp["address"] + peer_address = group_ntp["peer_address"] + prefer = group_ntp["prefer"] + vrf_name = group_ntp["vrf_name"] + key_id = group_ntp["key_id"] + + if prefer is not None: + prefer = "enabled" + else: + prefer = "disabled" + + if address is not None: + peer_type = "server" + elif peer_address is not None: + peer_type = "peer" + address = peer_address + + args = dict( + peer_type=peer_type, + address=address, + prefer=prefer, + vrf_name=vrf_name, + key_id=key_id, + ) + + ntp_peer = dict((k, v) for k, v in args.items()) + ntp_peer_list.append(ntp_peer) + except AttributeError: + ntp_peer_list = [] + + return ntp_peer_list + + +def get_ntp_existing(address, peer_type, module): + peer_dict = {} + peer_server_list = [] + + peer_list = get_ntp_peer(module) + for peer in peer_list: + if peer["address"] == address: + peer_dict.update(peer) + else: + peer_server_list.append(peer) + + source_type, source = get_ntp_source(module) + + if source_type is not None and source is not None: + peer_dict["source_type"] = source_type + peer_dict["source"] = source + + return (peer_dict, peer_server_list) + + +def set_ntp_server_peer(peer_type, address, prefer, key_id, vrf_name): + command_strings = [] + + if prefer: + command_strings.append(" prefer") + if key_id: + command_strings.append(" key {0}".format(key_id)) + if vrf_name: + command_strings.append(" use-vrf {0}".format(vrf_name)) + + command_strings.insert(0, "ntp {0} {1}".format(peer_type, address)) + + command = "".join(command_strings) + + return command + + +def config_ntp(delta, existing): + if ( + delta.get("address") + or delta.get("peer_type") + or delta.get("vrf_name") + or delta.get("key_id") + or delta.get("prefer") + ): + address = delta.get("address", existing.get("address")) + peer_type = delta.get("peer_type", existing.get("peer_type")) + key_id = delta.get("key_id", existing.get("key_id")) + prefer = delta.get("prefer", existing.get("prefer")) + vrf_name = delta.get("vrf_name", existing.get("vrf_name")) + if delta.get("key_id") == "default": + key_id = None + else: + peer_type = None + prefer = None + + source_type = delta.get("source_type") + source = delta.get("source") + + if prefer: + if prefer == "enabled": + prefer = True + elif prefer == "disabled": + prefer = False + + if source: + source_type = delta.get("source_type", existing.get("source_type")) + + ntp_cmds = [] + if peer_type: + if existing.get("peer_type") and existing.get("address"): + ntp_cmds.append( + "no ntp {0} {1}".format(existing.get("peer_type"), existing.get("address")), + ) + ntp_cmds.append(set_ntp_server_peer(peer_type, address, prefer, key_id, vrf_name)) + if source: + existing_source_type = existing.get("source_type") + existing_source = existing.get("source") + if existing_source_type and source_type != existing_source_type: + ntp_cmds.append("no ntp {0} {1}".format(existing_source_type, existing_source)) + if source == "default": + if existing_source_type and existing_source: + ntp_cmds.append("no ntp {0} {1}".format(existing_source_type, existing_source)) + else: + ntp_cmds.append("ntp {0} {1}".format(source_type, source)) + + return ntp_cmds + + +def main(): + argument_spec = dict( + server=dict(type="str"), + peer=dict(type="str"), + key_id=dict(type="str"), + prefer=dict(type="str", choices=["enabled", "disabled"]), + vrf_name=dict(type="str"), + source_addr=dict(type="str"), + source_int=dict(type="str"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[["server", "peer"], ["source_addr", "source_int"]], + supports_check_mode=True, + ) + + warnings = list() + + server = module.params["server"] or None + peer = module.params["peer"] or None + key_id = module.params["key_id"] + prefer = module.params["prefer"] + vrf_name = module.params["vrf_name"] + source_addr = module.params["source_addr"] + source_int = module.params["source_int"] + state = module.params["state"] + + if source_int is not None: + source_int = source_int.lower() + + if server: + peer_type = "server" + address = server + elif peer: + peer_type = "peer" + address = peer + else: + peer_type = None + address = None + + source_type = None + source = None + if source_addr: + source_type = "source" + source = source_addr + elif source_int: + source_type = "source-interface" + source = source_int + + if key_id or vrf_name or prefer: + if not server and not peer: + module.fail_json(msg="Please supply the server or peer parameter") + + args = dict( + peer_type=peer_type, + address=address, + key_id=key_id, + prefer=prefer, + vrf_name=vrf_name, + source_type=source_type, + source=source, + ) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + + existing, peer_server_list = get_ntp_existing(address, peer_type, module) + + end_state = existing + changed = False + commands = [] + + if state == "present": + delta = dict(set(proposed.items()).difference(existing.items())) + if delta.get("key_id") and delta.get("key_id") == "default": + if not existing.get("key_id"): + delta.pop("key_id") + if delta: + command = config_ntp(delta, existing) + if command: + commands.append(command) + + elif state == "absent": + if existing.get("peer_type") and existing.get("address"): + command = "no ntp {0} {1}".format(existing["peer_type"], existing["address"]) + if command: + commands.append([command]) + + existing_source_type = existing.get("source_type") + existing_source = existing.get("source") + proposed_source_type = proposed.get("source_type") + proposed_source = proposed.get("source") + + if proposed_source_type: + if proposed_source_type == existing_source_type: + if proposed_source == existing_source: + command = "no ntp {0} {1}".format(existing_source_type, existing_source) + if command: + commands.append([command]) + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + changed = True + load_config(module, cmds) + end_state = get_ntp_existing(address, peer_type, module)[0] + if "configure" in cmds: + cmds.pop(0) + + results = {} + results["proposed"] = proposed + results["existing"] = existing + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + results["end_state"] = end_state + results["peer_server_list"] = peer_server_list + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py new file mode 100644 index 00000000..3e564381 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_ntp_auth +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages NTP authentication. +description: +- Manages NTP authentication. +version_added: 1.0.0 +deprecated: + alternative: nxos_ntp_global + why: Updated module released with more functionality. + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +- If C(state=absent), the module will remove the given key configuration if it exists. +- If C(state=absent) and C(authentication=on), authentication will be turned off. +options: + key_id: + description: + - Authentication key identifier (numeric). + type: str + md5string: + description: + - MD5 String. + type: str + auth_type: + description: + - Whether the given md5string is in cleartext or has been encrypted. If in cleartext, + the device will encrypt it before storing it. + default: text + choices: + - text + - encrypt + type: str + trusted_key: + description: + - Whether the given key is required to be supplied by a time source for the device + to synchronize to the time source. + choices: + - 'false' + - 'true' + default: 'false' + type: str + authentication: + description: + - Turns NTP authentication on or off. + choices: + - "on" + - "off" + type: str + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# Basic NTP authentication configuration +- cisco.nxos.nxos_ntp_auth: + key_id: 32 + md5string: hello + auth_type: text +""" + +RETURN = """ +commands: + description: command sent to the device + returned: always + type: list + sample: ["ntp authentication-key 32 md5 helloWorld 0", "ntp trusted-key 32"] +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module): + if "show run" not in command: + command = {"command": command, "output": "json"} + else: + command = {"command": command, "output": "text"} + + return run_commands(module, [command]) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_ntp_auth(module): + command = "show ntp authentication-status" + + body = execute_show_command(command, module)[0] + ntp_auth_str = body["authentication"] + + if "enabled" in ntp_auth_str: + ntp_auth = True + else: + ntp_auth = False + + return ntp_auth + + +def get_ntp_trusted_key(module): + trusted_key_list = [] + command = "show run | inc ntp.trusted-key" + + trusted_key_str = execute_show_command(command, module)[0] + if trusted_key_str: + trusted_keys = trusted_key_str.splitlines() + + else: + trusted_keys = [] + + for line in trusted_keys: + if line: + trusted_key_list.append(str(line.split()[2])) + + return trusted_key_list + + +def get_ntp_auth_key(key_id, module): + authentication_key = {} + command = "show run | inc ntp.authentication-key.{0}".format(key_id) + auth_regex = ( + r".*ntp\sauthentication-key\s(?P<key_id>\d+)\smd5\s(?P<md5string>\S+)\s(?P<atype>\S+).*" + ) + + body = execute_show_command(command, module)[0] + + try: + match_authentication = re.match(auth_regex, body, re.DOTALL) + group_authentication = match_authentication.groupdict() + authentication_key["key_id"] = group_authentication["key_id"] + authentication_key["md5string"] = group_authentication["md5string"] + if group_authentication["atype"] == "7": + authentication_key["auth_type"] = "encrypt" + else: + authentication_key["auth_type"] = "text" + except (AttributeError, TypeError): + authentication_key = {} + + return authentication_key + + +def get_ntp_auth_info(key_id, module): + auth_info = get_ntp_auth_key(key_id, module) + trusted_key_list = get_ntp_trusted_key(module) + auth_power = get_ntp_auth(module) + + if key_id in trusted_key_list: + auth_info["trusted_key"] = "true" + else: + auth_info["trusted_key"] = "false" + + if auth_power: + auth_info["authentication"] = "on" + else: + auth_info["authentication"] = "off" + + return auth_info + + +def auth_type_to_num(auth_type): + if auth_type == "encrypt": + return "7" + else: + return "0" + + +def set_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication): + ntp_auth_cmds = [] + if key_id and md5string: + auth_type_num = auth_type_to_num(auth_type) + ntp_auth_cmds.append( + "ntp authentication-key {0} md5 {1} {2}".format(key_id, md5string, auth_type_num), + ) + + if trusted_key == "true": + ntp_auth_cmds.append("ntp trusted-key {0}".format(key_id)) + elif trusted_key == "false": + ntp_auth_cmds.append("no ntp trusted-key {0}".format(key_id)) + + if authentication == "on": + ntp_auth_cmds.append("ntp authenticate") + elif authentication == "off": + ntp_auth_cmds.append("no ntp authenticate") + + return ntp_auth_cmds + + +def remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication): + auth_remove_cmds = [] + if key_id: + auth_type_num = auth_type_to_num(auth_type) + auth_remove_cmds.append( + "no ntp authentication-key {0} md5 {1} {2}".format(key_id, md5string, auth_type_num), + ) + + if authentication: + auth_remove_cmds.append("no ntp authenticate") + return auth_remove_cmds + + +def main(): + argument_spec = dict( + key_id=dict(type="str"), + md5string=dict(type="str"), + auth_type=dict(choices=["text", "encrypt"], default="text"), + trusted_key=dict(choices=["true", "false"], default="false"), + authentication=dict(choices=["on", "off"]), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + key_id = module.params["key_id"] + md5string = module.params["md5string"] + auth_type = module.params["auth_type"] + trusted_key = module.params["trusted_key"] + authentication = module.params["authentication"] + state = module.params["state"] + + if key_id: + if not trusted_key and not md5string: + module.fail_json(msg="trusted_key or md5string MUST be specified") + + args = dict( + key_id=key_id, + md5string=md5string, + auth_type=auth_type, + trusted_key=trusted_key, + authentication=authentication, + ) + + changed = False + proposed = dict((k, v) for k, v in args.items() if v is not None) + + existing = get_ntp_auth_info(key_id, module) + end_state = existing + + delta = dict(set(proposed.items()).difference(existing.items())) + + commands = [] + if state == "present": + if delta: + command = set_ntp_auth_key( + key_id, + md5string, + delta.get("auth_type"), + delta.get("trusted_key"), + delta.get("authentication"), + ) + if command: + commands.append(command) + elif state == "absent": + auth_toggle = None + if existing.get("authentication") == "on": + auth_toggle = True + if not existing.get("key_id"): + key_id = None + command = remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, auth_toggle) + if command: + commands.append(command) + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + load_config(module, cmds) + end_state = get_ntp_auth_info(key_id, module) + delta = dict(set(end_state.items()).difference(existing.items())) + if delta or (len(existing) != len(end_state)): + changed = True + if "configure" in cmds: + cmds.pop(0) + + results = {} + results["proposed"] = proposed + results["existing"] = existing + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + results["end_state"] = end_state + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py new file mode 100644 index 00000000..e99fbef8 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py @@ -0,0 +1,736 @@ +#!/usr/bin/python +# -*- 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 module file for nxos_ntp_global +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_ntp_global +short_description: NTP Global resource module. +description: +- This module manages ntp configuration on devices running Cisco NX-OS. +version_added: 2.6.0 +notes: +- Tested against NX-OS 9.3.6 on Cisco Nexus Switches. +- This module works with connection C(network_cli) and C(httpapi). +- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config ntp). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dict of ntp configuration. + type: dict + suboptions: + access_group: + description: + - NTP access-group. + - This option is unsupported on MDS switches. + type: dict + suboptions: + match_all: + description: Scan ACLs present in all ntp access groups. + type: bool + peer: + description: Access-group peer. + type: list + elements: dict + suboptions: + access_list: + description: Name of access list. + type: str + query_only: + description: Access-group query-only. + type: list + elements: dict + suboptions: + access_list: + description: Name of access list. + type: str + serve: + description: Access-group serve. + type: list + elements: dict + suboptions: + access_list: + description: Name of access list. + type: str + serve_only: + description: Access-group serve-only. + type: list + elements: dict + suboptions: + access_list: + description: Name of access list. + type: str + allow: + description: Enable/Disable the packets. + type: dict + suboptions: + control: + description: Control mode packets. + type: dict + suboptions: + rate_limit: + description: Rate-limit delay. + type: int + private: + description: Enable/Disable Private mode packets. + type: bool + authenticate: + description: Enable/Disable authentication. + type: bool + authentication_keys: + description: NTP authentication key. + type: list + elements: dict + suboptions: + id: + description: Authentication key number (range 1-65535). + type: int + key: + description: Authentication key. + type: str + encryption: + description: + - 0 for Clear text + - 7 for Encrypted + type: int + logging: + description: Enable/Disable logging of NTPD Events. + type: bool + master: + description: + - Act as NTP master clock. + - This option is unsupported on MDS switches. + type: dict + suboptions: + stratum: + description: Stratum number. + type: int + passive: + description: + - NTP passive command. + - This option is unsupported on MDS switches. + type: bool + peers: + description: NTP Peers. + type: list + elements: dict + suboptions: + peer: + description: Hostname/IP address of the NTP Peer. + type: str + key_id: + description: Keyid to be used while communicating to this server. + type: int + maxpoll: + description: + - Maximum interval to poll a peer. + - Poll interval in secs to a power of 2. + type: int + minpoll: + description: + - Minimum interval to poll a peer. + - Poll interval in secs to a power of 2. + type: int + prefer: + description: + - Preferred Server. + type: bool + vrf: + description: + - Display per-VRF information. + - This option is unsupported on MDS switches. + type: str + aliases: ["use_vrf"] + servers: + description: NTP servers. + type: list + elements: dict + suboptions: + server: + description: Hostname/IP address of the NTP Peer. + type: str + key_id: + description: Keyid to be used while communicating to this server. + type: int + maxpoll: + description: + - Maximum interval to poll a peer. + - Poll interval in secs to a power of 2. + type: int + minpoll: + description: + - Minimum interval to poll a peer. + - Poll interval in secs to a power of 2. + type: int + prefer: + description: + - Preferred Server. + type: bool + vrf: + description: + - Display per-VRF information. + - This option is not applicable for MDS switches. + type: str + aliases: ["use_vrf"] + source: + description: + - Source of NTP packets. + - This option is unsupported on MDS switches. + type: str + source_interface: + description: Source interface sending NTP packets. + type: str + trusted_keys: + description: NTP trusted-key number. + type: list + elements: dict + suboptions: + key_id: + description: Trusted-Key number. + type: int + state: + description: + - The state the configuration should be left in. + - The states I(replaced) and I(overridden) have identical + behaviour for this module. + - Please refer to examples for more details. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - parsed + - gathered + - rendered + default: merged +""" + +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# nxos-9k-rdo# show running-config ntp +# nxos-9k-rdo# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_ntp_global: &id001 + config: + access_group: + peer: + - access_list: PeerAcl1 + serve: + - access_list: ServeAcl1 + authenticate: True + authentication_keys: + - id: 1001 + key: vagwwtKfkv + encryption: 7 + - id: 1002 + key: vagwwtKfkvgthz + encryption: 7 + logging: True + master: + stratum: 2 + peers: + - peer: 192.0.2.1 + key_id: 1 + maxpoll: 15 + minpoll: 5 + vrf: default + - peer: 192.0.2.2 + key_id: 2 + prefer: True + vrf: siteA + servers: + - server: 198.51.100.1 + key_id: 2 + vrf: default + - server: 203.0.113.1 + key_id: 1 + vrf: siteB + +# Task output +# ------------- +# before: {} +# +# commands: +# - "ntp authenticate" +# - "ntp logging" +# - "ntp master 2" +# - "ntp authentication-keys 1001 md5 vagwwtKfkv 7" +# - "ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7" +# - "ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15" +# - "ntp peer 192.0.2.2 prefer use-vrf siteA key 2" +# - "ntp server 198.51.100.1 use-vrf default key 2" +# - "ntp server 203.0.113.1 use-vrf siteB key 1" +# - "ntp access-group peer PeerAcl1" +# - "ntp access-group serve ServeAcl1" +# +# after: +# access_group: +# peer: +# - access_list: PeerAcl1 +# serve: +# - access_list: ServeAcl1 +# authenticate: True +# authentication_keys: +# - id: 1001 +# key: vagwwtKfkv +# encryption: 7 +# - id: 1002 +# key: vagwwtKfkvgthz +# encryption: 7 +# logging: True +# master: +# stratum: 2 +# peers: +# - peer: 192.0.2.1 +# key_id: 1 +# maxpoll: 15 +# minpoll: 5 +# vrf: default +# - peer: 192.0.2.2 +# key_id: 2 +# prefer: True +# vrf: siteA +# servers: +# - server: 198.51.100.1 +# key_id: 2 +# vrf: default +# - server: 203.0.113.1 +# key_id: 1 +# vrf: siteB + +# After state: +# ------------ +# nxos-9k-rdo# show running-config ntp +# ntp authenticate +# ntp logging +# ntp master 2 +# ntp authentication-keys 1001 md5 vagwwtKfkv 7 +# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7 +# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15 +# ntp peer 192.0.2.2 prefer use-vrf siteA key 2 +# ntp server 198.51.100.1 use-vrf default key 2 +# ntp server 203.0.113.1 use-vrf siteB key 1 +# ntp access-group peer PeerAcl1 +# ntp access-group serve ServeAcl1 + +# Using replaced + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config ntp +# ntp authenticate +# ntp logging +# ntp master 2 +# ntp authentication-keys 1001 md5 vagwwtKfkv 7 +# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7 +# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15 +# ntp peer 192.0.2.2 prefer use-vrf siteA key 2 +# ntp server 198.51.100.1 use-vrf default key 2 +# ntp server 203.0.113.1 use-vrf siteB key 1 +# ntp access-group peer PeerAcl1 +# ntp access-group serve ServeAcl1 + +- name: Replace logging global configurations of listed logging global with provided configurations + cisco.nxos.nxos_ntp_global: + config: + access_group: + peer: + - access_list: PeerAcl2 + serve: + - access_list: ServeAcl2 + logging: True + master: + stratum: 2 + peers: + - peer: 192.0.2.1 + key_id: 1 + maxpoll: 15 + minpoll: 5 + vrf: default + - peer: 192.0.2.5 + key_id: 2 + prefer: True + vrf: siteA + servers: + - server: 198.51.100.1 + key_id: 2 + vrf: default + state: replaced + +# Task output +# ------------- +# before: +# access_group: +# peer: +# - access_list: PeerAcl1 +# serve: +# - access_list: ServeAcl1 +# authenticate: True +# authentication_keys: +# - id: 1001 +# key: vagwwtKfkv +# encryption: 7 +# - id: 1002 +# key: vagwwtKfkvgthz +# encryption: 7 +# logging: True +# master: +# stratum: 2 +# peers: +# - peer: 192.0.2.1 +# key_id: 1 +# maxpoll: 15 +# minpoll: 5 +# vrf: default +# - peer: 192.0.2.2 +# key_id: 2 +# prefer: True +# vrf: siteA +# servers: +# - server: 198.51.100.1 +# key_id: 2 +# vrf: default +# - server: 203.0.113.1 +# key_id: 1 +# vrf: siteB +# +# commands: +# - "no ntp authenticate" +# - "no ntp authentication-keys 1001 md5 vagwwtKfkv 7" +# - "no ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7" +# - "ntp peer 192.0.2.5 prefer use-vrf siteA key 2" +# - "no ntp peer 192.0.2.2 prefer use-vrf siteA key 2" +# - "no ntp server 203.0.113.1 use-vrf siteB key 1" +# - "ntp access-group peer PeerAcl2" +# - "no ntp access-group peer PeerAcl1" +# - "ntp access-group serve ServeAcl2" +# - "no ntp access-group serve ServeAcl1" +# +# after: +# access_group: +# peer: +# - access_list: PeerAcl2 +# serve: +# - access_list: ServeAcl2 +# logging: True +# master: +# stratum: 2 +# peers: +# - peer: 192.0.2.1 +# key_id: 1 +# maxpoll: 15 +# minpoll: 5 +# vrf: default +# - peer: 192.0.2.5 +# key_id: 2 +# prefer: True +# vrf: siteA +# servers: +# - server: 198.51.100.1 +# key_id: 2 +# vrf: default + +# After state: +# ------------ +# nxos-9k-rdo# show running-config ntp +# ntp logging +# ntp master 2 +# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15 +# ntp peer 192.0.2.5 prefer use-vrf siteA key 2 +# ntp server 198.51.100.1 use-vrf default key 2 +# ntp access-group peer PeerAcl2 +# ntp access-group serve ServeAcl2 + +# Using deleted to delete all logging configurations + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config ntp + +- name: Delete all logging configuration + cisco.nxos.nxos_ntp_global: + state: deleted + +# Task output +# ------------- +# before: +# access_group: +# peer: +# - access_list: PeerAcl1 +# serve: +# - access_list: ServeAcl1 +# authenticate: True +# authentication_keys: +# - id: 1001 +# key: vagwwtKfkv +# encryption: 7 +# - id: 1002 +# key: vagwwtKfkvgthz +# encryption: 7 +# logging: True +# master: +# stratum: 2 +# peers: +# - peer: 192.0.2.1 +# key_id: 1 +# maxpoll: 15 +# minpoll: 5 +# vrf: default +# - peer: 192.0.2.2 +# key_id: 2 +# prefer: True +# vrf: siteA +# servers: +# - server: 198.51.100.1 +# key_id: 2 +# vrf: default +# - server: 203.0.113.1 +# key_id: 1 +# vrf: siteB +# +# commands: +# - "no ntp authenticate" +# - "no ntp logging" +# - "no ntp master 2" +# - "no ntp authentication-keys 1001 md5 vagwwtKfkv 7" +# - "no ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7" +# - "no ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15" +# - "no ntp peer 192.0.2.2 prefer use-vrf siteA key 2" +# - "no ntp server 198.51.100.1 use-vrf default key 2" +# - "no ntp server 203.0.113.1 use-vrf siteB key 1" +# - "no ntp access-group peer PeerAcl1" +# - "no ntp access-group serve ServeAcl1" +# +# after: {} + +# After state: +# ------------ +# nxos-9k-rdo# show running-config ntp +# nxos-9k-rdo# + +# Using rendered + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_ntp_global: + config: + access_group: + peer: + - access_list: PeerAcl1 + serve: + - access_list: ServeAcl1 + authenticate: True + authentication_keys: + - id: 1001 + key: vagwwtKfkv + encryption: 7 + - id: 1002 + key: vagwwtKfkvgthz + encryption: 7 + logging: True + master: + stratum: 2 + peers: + - peer: 192.0.2.1 + key_id: 1 + maxpoll: 15 + minpoll: 5 + vrf: default + - peer: 192.0.2.2 + key_id: 2 + prefer: True + vrf: siteA + servers: + - server: 198.51.100.1 + key_id: 2 + vrf: default + - server: 203.0.113.1 + key_id: 1 + vrf: siteB + state: rendered + +# Task Output (redacted) +# ----------------------- +# rendered: +# - "ntp authenticate" +# - "ntp logging" +# - "ntp master 2" +# - "ntp authentication-keys 1001 md5 vagwwtKfkv 7" +# - "ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7" +# - "ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15" +# - "ntp peer 192.0.2.2 prefer use-vrf siteA key 2" +# - "ntp server 198.51.100.1 use-vrf default key 2" +# - "ntp server 203.0.113.1 use-vrf siteB key 1" +# - "ntp access-group peer PeerAcl1" +# - "ntp access-group serve ServeAcl1" + +# Using parsed + +# parsed.cfg +# ------------ +# ntp authenticate +# ntp logging +# ntp master 2 +# ntp authentication-keys 1001 md5 vagwwtKfkv 7 +# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7 +# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15 +# ntp peer 192.0.2.2 prefer use-vrf siteA key 2 +# ntp server 198.51.100.1 use-vrf default key 2 +# ntp server 203.0.113.1 use-vrf siteB key 1 +# ntp access-group peer PeerAcl1 +# ntp access-group serve ServeAcl1 + +- name: Parse externally provided ntp configuration + cisco.nxos.nxos_ntp_global: + running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# access_group: +# peer: +# - access_list: PeerAcl1 +# serve: +# - access_list: ServeAcl1 +# authenticate: True +# authentication_keys: +# - id: 1001 +# key: vagwwtKfkv +# encryption: 7 +# - id: 1002 +# key: vagwwtKfkvgthz +# encryption: 7 +# logging: True +# master: +# stratum: 2 +# peers: +# - peer: 192.0.2.1 +# key_id: 1 +# maxpoll: 15 +# minpoll: 5 +# vrf: default +# - peer: 192.0.2.2 +# key_id: 2 +# prefer: True +# vrf: siteA +# servers: +# - server: 198.51.100.1 +# key_id: 2 +# vrf: default +# - server: 203.0.113.1 +# key_id: 1 +# vrf: siteB +""" + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: dict + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: list + sample: + - ntp master stratum 2 + - ntp peer 198.51.100.1 use-vrf test maxpoll 7 + - ntp authentication-key 10 md5 wawyhanx2 7 + - ntp access-group peer PeerAcl1 + - ntp access-group peer PeerAcl2 + - ntp access-group query-only QueryAcl1 +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - ntp master stratum 2 + - ntp peer 198.51.100.1 use-vrf test maxpoll 7 + - ntp authentication-key 10 md5 wawyhanx2 7 + - ntp access-group peer PeerAcl1 + - ntp access-group peer PeerAcl2 + - ntp access-group query-only QueryAcl1 +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: list + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: list + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ntp_global.ntp_global import ( + Ntp_globalArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ntp_global.ntp_global import ( + Ntp_global, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Ntp_globalArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Ntp_global(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py new file mode 100644 index 00000000..28fd1aac --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_ntp_options +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages NTP options. +description: +- Manages NTP options, e.g. authoritative server and logging. +version_added: 1.0.0 +deprecated: + alternative: nxos_ntp_global + why: Updated module released with more functionality. + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +- When C(state=absent), master and logging will be set to False and stratum will be + removed as well +options: + master: + description: + - Sets whether the device is an authoritative NTP server. + type: bool + stratum: + description: + - If C(master=true), an optional stratum can be supplied (1-15). The device default + is 8. + type: str + logging: + description: + - Sets whether NTP logging is enabled on the device. + type: bool + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - absent + type: str +""" +EXAMPLES = """ +# Basic NTP options configuration +- cisco.nxos.nxos_ntp_options: + master: true + stratum: 12 + logging: false + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +""" + +RETURN = """ +updates: + description: command sent to the device + returned: always + type: list + sample: ["no ntp logging", "ntp master 12"] +""" +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def get_current(module): + cmd = "show running-config | inc ntp" + + master = False + logging = False + stratum = None + + output = run_commands(module, ({"command": cmd, "output": "text"}))[0] + + if output: + match = re.search(r"^ntp master(?: (\d+))", output, re.M) + if match: + master = True + stratum = match.group(1) + logging = "ntp logging" in output.lower() + + return {"master": master, "stratum": stratum, "logging": logging} + + +def main(): + argument_spec = dict( + master=dict(required=False, type="bool"), + stratum=dict(required=False, type="str"), + logging=dict(required=False, type="bool"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + master = module.params["master"] + stratum = module.params["stratum"] + logging = module.params["logging"] + state = module.params["state"] + + if stratum and master is False: + if stratum != 8: + module.fail_json(msg="master MUST be True when stratum is changed") + + current = get_current(module) + + result = {"changed": False} + + commands = list() + + if state == "absent": + if current["master"]: + commands.append("no ntp master") + if current["logging"]: + commands.append("no ntp logging") + + elif state == "present": + if master and not current["master"]: + commands.append("ntp master") + elif master is False and current["master"]: + commands.append("no ntp master") + if stratum and stratum != current["stratum"]: + commands.append("ntp master %s" % stratum) + + if logging and not current["logging"]: + commands.append("ntp logging") + elif logging is False and current["logging"]: + commands.append("no ntp logging") + + result["commands"] = commands + result["updates"] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result["changed"] = True + + result["warnings"] = warnings + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py new file mode 100644 index 00000000..cfd7cc87 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py @@ -0,0 +1,424 @@ +#!/usr/bin/python +# +# 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/>. +# pylint: skip-file +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_nxapi +extends_documentation_fragment: +- cisco.nxos.nxos +author: Peter Sprygada (@privateip) +short_description: Manage NXAPI configuration on an NXOS device. +notes: +- Limited Support for Cisco MDS +description: +- Configures the NXAPI feature on devices running Cisco NXOS. The NXAPI feature is + absent from the configuration by default. Since this module manages the NXAPI feature + it only supports the use of the C(Cli) transport. +version_added: 1.0.0 +options: + http_port: + description: + - Configure the port with which the HTTP server will listen on for requests. By + default, NXAPI will bind the HTTP service to the standard HTTP port 80. This + argument accepts valid port values in the range of 1 to 65535. + required: false + default: 80 + type: int + http: + description: + - Controls the operating state of the HTTP protocol as one of the underlying transports + for NXAPI. By default, NXAPI will enable the HTTP transport when the feature + is first configured. To disable the use of the HTTP transport, set the value + of this argument to False. + required: false + default: true + type: bool + aliases: + - enable_http + https_port: + description: + - Configure the port with which the HTTPS server will listen on for requests. By + default, NXAPI will bind the HTTPS service to the standard HTTPS port 443. This + argument accepts valid port values in the range of 1 to 65535. + required: false + default: 443 + type: int + https: + description: + - Controls the operating state of the HTTPS protocol as one of the underlying + transports for NXAPI. By default, NXAPI will disable the HTTPS transport when + the feature is first configured. To enable the use of the HTTPS transport, + set the value of this argument to True. + required: false + default: false + type: bool + aliases: + - enable_https + sandbox: + description: + - The NXAPI feature provides a web base UI for developers for entering commands. This + feature is initially disabled when the NXAPI feature is configured for the first + time. When the C(sandbox) argument is set to True, the developer sandbox URL + will accept requests and when the value is set to False, the sandbox URL is + unavailable. This is supported on NX-OS 7K series. + required: false + type: bool + aliases: + - enable_sandbox + state: + description: + - The C(state) argument controls whether or not the NXAPI feature is configured + on the remote device. When the value is C(present) the NXAPI feature configuration + is present in the device running-config. When the values is C(absent) the feature + configuration is removed from the running-config. + choices: + - present + - absent + required: false + default: present + type: str + ssl_strong_ciphers: + description: + - Controls the use of whether strong or weak ciphers are configured. By default, + this feature is disabled and weak ciphers are configured. To enable the use + of strong ciphers, set the value of this argument to True. + required: false + default: false + type: bool + tlsv1_0: + description: + - Controls the use of the Transport Layer Security version 1.0 is configured. By + default, this feature is enabled. To disable the use of TLSV1.0, set the value + of this argument to True. + required: false + default: true + type: bool + tlsv1_1: + description: + - Controls the use of the Transport Layer Security version 1.1 is configured. By + default, this feature is disabled. To enable the use of TLSV1.1, set the value + of this argument to True. + required: false + default: false + type: bool + tlsv1_2: + description: + - Controls the use of the Transport Layer Security version 1.2 is configured. By + default, this feature is disabled. To enable the use of TLSV1.2, set the value + of this argument to True. + required: false + default: false + type: bool +""" + +EXAMPLES = """ +- name: Enable NXAPI access with default configuration + cisco.nxos.nxos_nxapi: + state: present + +- name: Enable NXAPI with no HTTP, HTTPS at port 9443 and sandbox disabled + cisco.nxos.nxos_nxapi: + enable_http: false + https_port: 9443 + https: yes + enable_sandbox: no + +- name: remove NXAPI configuration + cisco.nxos.nxos_nxapi: + state: absent +""" + +RETURN = """ +updates: + description: + - Returns the list of commands that need to be pushed into the remote + device to satisfy the arguments + returned: always + type: list + sample: ['no feature nxapi'] +""" +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + load_config, + run_commands, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import Version + + +def check_args(module, warnings, capabilities): + network_api = capabilities.get("network_api", "nxapi") + if network_api == "nxapi": + module.fail_json(msg="module not supported over nxapi transport") + + os_platform = capabilities["device_info"]["network_os_platform"] + if "7K" not in os_platform and module.params["sandbox"]: + module.fail_json( + msg="sandbox or enable_sandbox is supported on NX-OS 7K series of switches", + ) + + state = module.params["state"] + + if state == "started": + module.params["state"] = "present" + warnings.append( + "state=started is deprecated and will be removed in a " + "a future release. Please use state=present instead", + ) + elif state == "stopped": + module.params["state"] = "absent" + warnings.append( + "state=stopped is deprecated and will be removed in a " + "a future release. Please use state=absent instead", + ) + + for key in ["http_port", "https_port"]: + if module.params[key] is not None: + if not 1 <= module.params[key] <= 65535: + module.fail_json(msg="%s must be between 1 and 65535" % key) + + return warnings + + +def map_obj_to_commands(want, have, module, warnings, capabilities): + send_commands = list() + commands = dict() + os_platform = None + os_version = None + + device_info = capabilities.get("device_info") + if device_info: + os_version = device_info.get("network_os_version") + if os_version: + os_version = os_version[:3] + os_platform = device_info.get("network_os_platform") + if os_platform: + os_platform = os_platform[:3] + + def needs_update(x): + return want.get(x) is not None and (want.get(x) != have.get(x)) + + if needs_update("state"): + if want["state"] == "absent": + return ["no feature nxapi"] + send_commands.append("feature nxapi") + elif want["state"] == "absent": + return send_commands + + for parameter in ["http", "https"]: + port_param = parameter + "_port" + if needs_update(parameter): + if want.get(parameter) is False: + commands[parameter] = "no nxapi %s" % parameter + else: + commands[parameter] = "nxapi %s port %s" % ( + parameter, + want.get(port_param), + ) + + if needs_update(port_param) and want.get(parameter) is True: + commands[parameter] = "nxapi %s port %s" % ( + parameter, + want.get(port_param), + ) + + if needs_update("sandbox"): + commands["sandbox"] = "nxapi sandbox" + if not want["sandbox"]: + commands["sandbox"] = "no %s" % commands["sandbox"] + + if os_platform and os_version: + if (os_platform == "N9K" or os_platform == "N3K") and Version(os_version) >= "9.2": + if needs_update("ssl_strong_ciphers"): + commands["ssl_strong_ciphers"] = "nxapi ssl ciphers weak" + if want["ssl_strong_ciphers"] is True: + commands["ssl_strong_ciphers"] = "no nxapi ssl ciphers weak" + + have_ssl_protocols = "" + want_ssl_protocols = "" + for key, value in { + "tlsv1_2": "TLSv1.2", + "tlsv1_1": "TLSv1.1", + "tlsv1_0": "TLSv1", + }.items(): + if needs_update(key): + if want.get(key) is True: + want_ssl_protocols = " ".join([want_ssl_protocols, value]) + elif have.get(key) is True: + have_ssl_protocols = " ".join([have_ssl_protocols, value]) + + if len(want_ssl_protocols) > 0: + commands["ssl_protocols"] = "nxapi ssl protocols%s" % ( + " ".join([want_ssl_protocols, have_ssl_protocols]) + ) + else: + warnings.append( + "os_version and/or os_platform keys from " + "platform capabilities are not available. " + "Any NXAPI SSL optional arguments will be ignored", + ) + + send_commands.extend(commands.values()) + + return send_commands + + +def parse_http(data): + http_res = [r"nxapi http port (\d+)"] + http_port = None + + for regex in http_res: + match = re.search(regex, data, re.M) + if match: + http_port = int(match.group(1)) + break + + return {"http": http_port is not None, "http_port": http_port} + + +def parse_https(data): + https_res = [r"nxapi https port (\d+)"] + https_port = None + + for regex in https_res: + match = re.search(regex, data, re.M) + if match: + https_port = int(match.group(1)) + break + + return {"https": https_port is not None, "https_port": https_port} + + +def parse_sandbox(data): + sandbox = [item for item in data.split("\n") if re.search(r".*sandbox.*", item)] + value = False + if sandbox and sandbox[0] == "nxapi sandbox": + value = True + return {"sandbox": value} + + +def parse_ssl_strong_ciphers(data): + ciphers_res = [r"(\w+) nxapi ssl ciphers weak"] + value = None + + for regex in ciphers_res: + match = re.search(regex, data, re.M) + if match: + value = match.group(1) + break + + return {"ssl_strong_ciphers": value == "no"} + + +def parse_ssl_protocols(data): + tlsv1_0 = re.search(r"(?<!\S)TLSv1(?!\S)", data, re.M) is not None + tlsv1_1 = re.search(r"(?<!\S)TLSv1.1(?!\S)", data, re.M) is not None + tlsv1_2 = re.search(r"(?<!\S)TLSv1.2(?!\S)", data, re.M) is not None + + return {"tlsv1_0": tlsv1_0, "tlsv1_1": tlsv1_1, "tlsv1_2": tlsv1_2} + + +def map_config_to_obj(module): + out = run_commands(module, ["show run all | inc nxapi"], check_rc=False)[0] + match = re.search(r"no feature nxapi", out, re.M) + # There are two possible outcomes when nxapi is disabled on nxos platforms. + # 1. Nothing is displayed in the running config. + # 2. The 'no feature nxapi' command is displayed in the running config. + if match or out == "": + return {"state": "absent"} + + out = str(out).strip() + + obj = {"state": "present"} + obj.update(parse_http(out)) + obj.update(parse_https(out)) + obj.update(parse_sandbox(out)) + obj.update(parse_ssl_strong_ciphers(out)) + obj.update(parse_ssl_protocols(out)) + + return obj + + +def map_params_to_obj(module): + obj = { + "http": module.params["http"], + "http_port": module.params["http_port"], + "https": module.params["https"], + "https_port": module.params["https_port"], + "sandbox": module.params["sandbox"], + "state": module.params["state"], + "ssl_strong_ciphers": module.params["ssl_strong_ciphers"], + "tlsv1_0": module.params["tlsv1_0"], + "tlsv1_1": module.params["tlsv1_1"], + "tlsv1_2": module.params["tlsv1_2"], + } + + return obj + + +def main(): + """main entry point for module execution""" + argument_spec = dict( + http=dict(aliases=["enable_http"], type="bool", default=True), + http_port=dict(type="int", default=80), + https=dict(aliases=["enable_https"], type="bool", default=False), + https_port=dict(type="int", default=443), + sandbox=dict(aliases=["enable_sandbox"], type="bool"), + state=dict(default="present", choices=["present", "absent"]), + ssl_strong_ciphers=dict(type="bool", default=False), + tlsv1_0=dict(type="bool", default=True), + tlsv1_1=dict(type="bool", default=False), + tlsv1_2=dict(type="bool", default=False), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + warning_msg = "Module nxos_nxapi currently defaults to configure 'http port 80'. " + warning_msg += "Default behavior is changing to configure 'https port 443'" + warning_msg += " when params 'http, http_port, https, https_port' are not set in the playbook" + module.deprecate(msg=warning_msg, date="2022-06-01", collection_name="cisco.nxos") + + capabilities = get_capabilities(module) + + check_args(module, warnings, capabilities) + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(want, have, module, warnings, capabilities) + + result = {"changed": False, "warnings": warnings, "commands": commands} + + if commands: + if not module.check_mode: + load_config(module, commands) + result["changed"] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py new file mode 100644 index 00000000..c0a754c7 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py @@ -0,0 +1,1455 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for nxos_ospf_interfaces +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_ospf_interfaces +version_added: 1.3.0 +short_description: OSPF Interfaces Resource Module. +description: +- This module manages OSPF(v2/v3) configuration of interfaces on devices running Cisco NX-OS. +notes: +- Unsupported for Cisco MDS +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section "^interface"). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of OSPF configuration for interfaces. + type: list + elements: dict + suboptions: + name: + description: + - Name/Identifier of the interface. + type: str + required: True + address_family: + description: + - OSPF settings on the interfaces in address-family context. + type: list + elements: dict + suboptions: + afi: + description: + - Address Family Identifier (AFI) for OSPF settings on the interfaces. + type: str + choices: ['ipv4', 'ipv6'] + required: True + processes: + description: + - Interfaces configuration for an OSPF process. + type: list + elements: dict + suboptions: + process_id: + description: + - OSPF process tag. + type: str + required: True + area: + description: + - Area associated with interface. + type: dict + suboptions: + area_id: + description: + - Area ID in IP address format. + type: str + required: True + secondaries: + description: + - Do not include secondary IPv4/IPv6 addresses. + type: bool + multi_areas: + description: + - Multi-Areas associated with interface. + - Valid values are Area Ids as an integer or IP address. + type: list + elements: str + multi_areas: + description: + - Multi-Areas associated with interface (not tied to OSPF process). + - Valid values are Area Ids as an integer or IP address. + type: list + elements: str + authentication: + description: + - Authentication settings on the interface. + type: dict + suboptions: + key_chain: + description: + - Authentication password key-chain. + type: str + message_digest: + description: + - Use message-digest authentication. + type: bool + enable: + description: + - Enable/disable authentication on the interface. + type: bool + null_auth: + description: + - Use null(disable) authentication. + type: bool + authentication_key: + description: + - Configure the authentication key for the interface. + type: dict + suboptions: + encryption: + description: + - 0 Specifies an UNENCRYPTED authentication key will follow. + - 3 Specifies an 3DES ENCRYPTED authentication key will follow. + - 7 Specifies a Cisco type 7 ENCRYPTED authentication key will follow. + type: int + key: + description: + - Authentication key. + - Valid values are Cisco type 7 ENCRYPTED password, 3DES ENCRYPTED password + and UNENCRYPTED (cleartext) password based on the value of encryption key. + type: str + required: True + message_digest_key: + description: + - Message digest authentication password (key) settings. + type: dict + suboptions: + key_id: + description: + - Key ID. + type: int + required: True + encryption: + description: + - 0 Specifies an UNENCRYPTED ospf password (key) will follow. + - 3 Specifies an 3DES ENCRYPTED ospf password (key) will follow. + - 7 Specifies a Cisco type 7 ENCRYPTED the ospf password (key) will follow. + type: int + key: + description: + - Authentication key. + - Valid values are Cisco type 7 ENCRYPTED password, 3DES ENCRYPTED password + and UNENCRYPTED (cleartext) password based on the value of encryption key. + type: str + required: True + cost: + description: + - Cost associated with interface. + type: int + dead_interval: + description: + - Dead interval value (in seconds). + type: int + hello_interval: + description: + - Hello interval value (in seconds). + type: int + instance: + description: + - Instance identifier. + type: int + mtu_ignore: + description: + - Enable/disable OSPF MTU mismatch detection. + type: bool + network: + description: + - Network type. + type: str + choices: ["broadcast", "point-to-point"] + passive_interface: + description: + - Suppress routing updates on the interface. + - This option is mutually exclusive with I(default_passive_interface). + type: bool + default_passive_interface: + description: + - Set passive-interface attribute on this interface to default. + - This option is mutually exclusive with I(passive_interface). + type: bool + priority: + description: + - Router priority. + type: int + retransmit_interval: + description: + - Packet retransmission interval. + type: int + shutdown: + description: + - Shutdown OSPF on this interface. + type: bool + transmit_delay: + description: + - Packet transmission delay. + type: int + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - parsed + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# interface Ethernet1/2 +# no switchport +# interface Ethernet1/3 +# no switchport + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_ospf_interfaces: + config: + - name: Ethernet1/1 + address_family: + - afi: ipv4 + processes: + - process_id: "100" + area: + area_id: 1.1.1.1 + secondaries: False + multi_areas: + - 11.11.11.11 + - afi: ipv6 + processes: + - process_id: "200" + area: + area_id: 2.2.2.2 + multi_areas: + - 21.0.0.0 + - process_id: "300" + multi_areas: + - 50.50.50.50 + multi_areas: + - 16.10.10.10 + - name: Ethernet1/2 + address_family: + - afi: ipv4 + authentication: + enable: True + key_chain: test-1 + message_digest_key: + key_id: 10 + encryption: 3 + key: abc01d272be25d29 + cost: 100 + - afi: ipv6 + network: broadcast + shutdown: True + - name: Ethernet1/3 + address_family: + - afi: ipv4 + authentication_key: + encryption: 7 + key: 12090404011C03162E + state: merged + +# Task output +# ------------- +# "before": [ +# { +# "name": "Ethernet1/1" +# }, +# { +# "name": "Ethernet1/2" +# }, +# { +# "name": "Ethernet1/3" +# }, +# ] +# +# "commands": [ +# "interface Ethernet1/1", +# "ip router ospf multi-area 11.11.11.11", +# "ip router ospf 100 area 1.1.1.1 secondaries none", +# "ipv6 router ospfv3 multi-area 16.10.10.10", +# "ipv6 router ospfv3 200 area 2.2.2.2", +# "ipv6 router ospfv3 200 multi-area 21.0.0.0", +# "ipv6 router ospfv3 300 multi-area 50.50.50.50", +# "interface Ethernet1/2", +# "ip ospf authentication key-chain test-1", +# "ip ospf authentication", +# "ip ospf message-digest-key 10 md5 3 abc01d272be25d29", +# "ip ospf cost 100", +# "ospfv3 network broadcast", +# "ospfv3 shutdown", +# "interface Ethernet1/3", +# "ip ospf authentication-key 7 12090404011C03162E" +# ] +# +# "after": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.11" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "multi_areas": [ +# "16.10.10.10" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "2.2.2.2" +# }, +# "multi_areas": [ +# "21.0.0.0" +# ], +# "process_id": "200" +# }, +# { +# "multi_areas": [ +# "50.50.50.50" +# ], +# "process_id": "300" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication_key": { +# "encryption": 7, +# "key": "12090404011C03162E" +# } +# } +# ], +# "name": "Ethernet1/3" +# }, +# ] + +# After state: +# ------------- +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.11 +# ipv6 router ospfv3 200 area 2.2.2.2 +# ipv6 router ospfv3 multi-area 16.10.10.10 +# ipv6 router ospfv3 200 multi-area 21.0.0.0 +# ipv6 router ospfv3 300 multi-area 50.50.50.50 +# interface Ethernet1/2 +# no switchport +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# no switchport +# ip ospf authentication-key 7 12090404011C03162E + + +# Using replaced + +# Before state: +# ------------ +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.11 +# ipv6 router ospfv3 200 area 2.2.2.2 +# ipv6 router ospfv3 multi-area 16.10.10.10 +# ipv6 router ospfv3 200 multi-area 21.0.0.0 +# ipv6 router ospfv3 300 multi-area 50.50.50.50 +# interface Ethernet1/2 +# no switchport +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# no switchport +# ip ospf authentication-key 7 12090404011C03162E + +- name: Replace OSPF configurations of listed interfaces with provided configurations + cisco.nxos.nxos_ospf_interfaces: + config: + - name: Ethernet1/1 + address_family: + - afi: ipv4 + processes: + - process_id: "100" + area: + area_id: 1.1.1.1 + secondaries: False + multi_areas: + - 11.11.11.12 + - name: Ethernet1/3 + state: replaced + +# Task output +# ------------- +# "before": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.11" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "multi_areas": [ +# "16.10.10.10" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "2.2.2.2" +# }, +# "multi_areas": [ +# "21.0.0.0" +# ], +# "process_id": "200" +# }, +# { +# "multi_areas": [ +# "50.50.50.50" +# ], +# "process_id": "300" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication_key": { +# "encryption": 7, +# "key": "12090404011C03162E" +# } +# } +# ], +# "name": "Ethernet1/3" +# }, +# ] +# +# "commands": [ +# "interface Ethernet1/1", +# "ip router ospf multi-area 11.11.11.12", +# "no ip router ospf multi-area 11.11.11.11", +# "no ipv6 router ospfv3 multi-area 16.10.10.10", +# "no ipv6 router ospfv3 200 area 2.2.2.2", +# "no ipv6 router ospfv3 200 multi-area 21.0.0.0", +# "no ipv6 router ospfv3 300 multi-area 50.50.50.50", +# "interface Ethernet1/3", +# "no ip ospf authentication-key 7 12090404011C03162E" +# ] +# +# "after": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.12" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "name": "Ethernet1/3" +# }, +# +# After state: +# ------------- +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.12 +# interface Ethernet1/2 +# no switchport +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# no switchport + + +# Using overridden + +# Before state: +# ------------ +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.11 +# ipv6 router ospfv3 200 area 2.2.2.2 +# ipv6 router ospfv3 multi-area 16.10.10.10 +# ipv6 router ospfv3 200 multi-area 21.0.0.0 +# ipv6 router ospfv3 300 multi-area 50.50.50.50 +# interface Ethernet1/2 +# no switchport +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# no switchport +# ip ospf authentication-key 7 12090404011C03162E + +- name: Override all OSPF interfaces configuration with provided configuration + cisco.nxos.nxos_ospf_interfaces: + config: + - name: Ethernet1/1 + address_family: + - afi: ipv4 + processes: + - process_id: "100" + area: + area_id: 1.1.1.1 + secondaries: False + multi_areas: + - 11.11.11.12 + state: overridden + +# Task output +# ------------- +# "before": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.11" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "multi_areas": [ +# "16.10.10.10" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "2.2.2.2" +# }, +# "multi_areas": [ +# "21.0.0.0" +# ], +# "process_id": "200" +# }, +# { +# "multi_areas": [ +# "50.50.50.50" +# ], +# "process_id": "300" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication_key": { +# "encryption": 7, +# "key": "12090404011C03162E" +# } +# } +# ], +# "name": "Ethernet1/3" +# }, +# ] +# +# "commands": [ +# "interface Ethernet1/2", +# "no ip ospf authentication key-chain test-1", +# "no ip ospf authentication", +# "no ip ospf message-digest-key 10 md5 3 abc01d272be25d29", +# "no ip ospf cost 100", +# "no ospfv3 network broadcast", +# "no ospfv3 shutdown", +# "interface Ethernet1/3", +# "no ip ospf authentication-key 7 12090404011C03162E", +# "interface Ethernet1/1", +# "ip router ospf multi-area 11.11.11.12", +# "no ip router ospf multi-area 11.11.11.11", +# "no ipv6 router ospfv3 multi-area 16.10.10.10", +# "no ipv6 router ospfv3 200 area 2.2.2.2", +# "no ipv6 router ospfv3 200 multi-area 21.0.0.0", +# "no ipv6 router ospfv3 300 multi-area 50.50.50.50" +# ] +# +# "after": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.12" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "name": "Ethernet1/2" +# }, +# { +# "name": "Ethernet1/3" +# }, +# ] + +# After state: +# ------------- +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.12 +# interface Ethernet1/2 +# no switchport +# interface Ethernet1/3 +# no switchport + +# Using deleted to delete OSPF config of a single interface + +# Before state: +# ------------ +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.11 +# ipv6 router ospfv3 200 area 2.2.2.2 +# ipv6 router ospfv3 multi-area 16.10.10.10 +# ipv6 router ospfv3 200 multi-area 21.0.0.0 +# ipv6 router ospfv3 300 multi-area 50.50.50.50 +# interface Ethernet1/2 +# no switchport +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# no switchport +# ip ospf authentication-key 7 12090404011C03162E + +- name: Delete OSPF config from a single interface + cisco.nxos.nxos_ospf_interfaces: + config: + - name: Ethernet1/1 + state: deleted + +# Task output +# ------------- +# "before": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.11" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "multi_areas": [ +# "16.10.10.10" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "2.2.2.2" +# }, +# "multi_areas": [ +# "21.0.0.0" +# ], +# "process_id": "200" +# }, +# { +# "multi_areas": [ +# "50.50.50.50" +# ], +# "process_id": "300" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication_key": { +# "encryption": 7, +# "key": "12090404011C03162E" +# } +# } +# ], +# "name": "Ethernet1/3" +# }, +# ] +# +# "commands": [ +# "interface Ethernet1/1", +# "no ip router ospf multi-area 11.11.11.11", +# "no ip router ospf 100 area 1.1.1.1 secondaries none", +# "no ipv6 router ospfv3 multi-area 16.10.10.10", +# "no ipv6 router ospfv3 200 area 2.2.2.2", +# "no ipv6 router ospfv3 200 multi-area 21.0.0.0", +# "no ipv6 router ospfv3 300 multi-area 50.50.50.50" +# ] +# +# "before": [ +# { +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication_key": { +# "encryption": 7, +# "key": "12090404011C03162E" +# } +# } +# ], +# "name": "Ethernet1/3" +# }, +# ] + +# After state: +# ------------ +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# interface Ethernet1/2 +# no switchport +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# no switchport +# ip ospf authentication-key 7 12090404011C03162E + +# Using deleted to delete OSPF config from all interfaces + +# Before state: +# ------------ +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.11 +# ipv6 router ospfv3 200 area 2.2.2.2 +# ipv6 router ospfv3 multi-area 16.10.10.10 +# ipv6 router ospfv3 200 multi-area 21.0.0.0 +# ipv6 router ospfv3 300 multi-area 50.50.50.50 +# interface Ethernet1/2 +# no switchport +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# no switchport +# ip ospf authentication-key 7 12090404011C03162E + +- name: Delete OSPF config from all interfaces + cisco.nxos.nxos_ospf_interfaces: + state: deleted + +# Task output +# ------------- +# "before": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.11" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "multi_areas": [ +# "16.10.10.10" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "2.2.2.2" +# }, +# "multi_areas": [ +# "21.0.0.0" +# ], +# "process_id": "200" +# }, +# { +# "multi_areas": [ +# "50.50.50.50" +# ], +# "process_id": "300" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication_key": { +# "encryption": 7, +# "key": "12090404011C03162E" +# } +# } +# ], +# "name": "Ethernet1/3" +# }, +# ] +# +# "commands": [ +# "interface Ethernet1/1", +# "no ip router ospf multi-area 11.11.11.11", +# "no ip router ospf 100 area 1.1.1.1 secondaries none", +# "no ipv6 router ospfv3 multi-area 16.10.10.10", +# "no ipv6 router ospfv3 200 area 2.2.2.2", +# "no ipv6 router ospfv3 200 multi-area 21.0.0.0", +# "no ipv6 router ospfv3 300 multi-area 50.50.50.50", +# "interface Ethernet1/2", +# "no ip ospf authentication key-chain test-1", +# "no ip ospf authentication", +# "no ip ospf message-digest-key 10 md5 3 abc01d272be25d29", +# "no ip ospf cost 100", +# "no ospfv3 network broadcast", +# "no ospfv3 shutdown", +# "interface Ethernet1/3", +# "no ip ospf authentication-key 7 12090404011C03162E" +# ] +# +# "after": [ +# { +# "name": "Ethernet1/1" +# }, +# { +# "name": "Ethernet1/2" +# }, +# { +# "name": "Ethernet1/3" +# }, +# ] + +# After state: +# ------------ +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# interface Ethernet1/2 +# no switchport +# interface Ethernet1/3 +# no switchport + +# Using rendered + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_ospf_interfaces: + config: + - name: Ethernet1/1 + address_family: + - afi: ipv4 + processes: + - process_id: "100" + area: + area_id: 1.1.1.1 + secondaries: False + multi_areas: + - 11.11.11.11 + - afi: ipv6 + processes: + - process_id: "200" + area: + area_id: 2.2.2.2 + multi_areas: + - 21.0.0.0 + - process_id: "300" + multi_areas: + - 50.50.50.50 + multi_areas: + - 16.10.10.10 + - name: Ethernet1/2 + address_family: + - afi: ipv4 + authentication: + enable: True + key_chain: test-1 + message_digest_key: + key_id: 10 + encryption: 3 + key: abc01d272be25d29 + cost: 100 + - afi: ipv6 + network: broadcast + shutdown: True + - name: Ethernet1/3 + address_family: + - afi: ipv4 + authentication_key: + encryption: 7 + key: 12090404011C03162E + state: rendered + +# Task Output (redacted) +# ----------------------- +# "rendered": [ +# "interface Ethernet1/1", +# "ip router ospf multi-area 11.11.11.11", +# "ip router ospf 100 area 1.1.1.1 secondaries none", +# "ipv6 router ospfv3 multi-area 16.10.10.10", +# "ipv6 router ospfv3 200 area 2.2.2.2", +# "ipv6 router ospfv3 200 multi-area 21.0.0.0", +# "ipv6 router ospfv3 300 multi-area 50.50.50.50", +# "interface Ethernet1/2", +# "ip ospf authentication key-chain test-1", +# "ip ospf authentication", +# "ip ospf message-digest-key 10 md5 3 abc01d272be25d29", +# "ip ospf cost 100", +# "ospfv3 network broadcast", +# "ospfv3 shutdown", +# "interface Ethernet1/3", +# "ip ospf authentication-key 7 12090404011C03162E" +# ] + +# Using parsed + +# parsed.cfg +# ------------ +# interface Ethernet1/1 +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.11 +# ipv6 router ospfv3 200 area 2.2.2.2 +# ipv6 router ospfv3 200 multi-area 21.0.0.0 +# ipv6 router ospfv3 300 multi-area 50.50.50.50 +# ipv6 router ospfv3 multi-area 16.10.10.10 +# interface Ethernet1/2 +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# ip ospf authentication-key 7 12090404011C03162E + +- name: arse externally provided OSPF interfaces config + cisco.nxos.nxos_ospf_interfaces: + running_config: "{{ lookup('file', 'ospf_interfaces.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# "parsed": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.11" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "multi_areas": [ +# "16.10.10.10" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "2.2.2.2" +# }, +# "multi_areas": [ +# "21.0.0.0" +# ], +# "process_id": "200" +# }, +# { +# "multi_areas": [ +# "50.50.50.50" +# ], +# "process_id": "300" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication_key": { +# "encryption": 7, +# "key": "12090404011C03162E" +# } +# } +# ], +# "name": "Ethernet1/3" +# }, +# ] + +# Using gathered + +# On-box config + +# NXOS# show running-config | section ^interface +# interface Ethernet1/1 +# no switchport +# ip router ospf 100 area 1.1.1.1 secondaries none +# ip router ospf multi-area 11.11.11.12 +# interface Ethernet1/2 +# no switchport +# ip ospf authentication +# ip ospf authentication key-chain test-1 +# ip ospf message-digest-key 10 md5 3 abc01d272be25d29 +# ip ospf cost 100 +# ospfv3 network broadcast +# ospfv3 shutdown +# interface Ethernet1/3 +# no switchport + +# Task output (redacted) +# ----------------------- +# "gathered": [ +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "multi_areas": [ +# "11.11.11.12" +# ], +# "processes": [ +# { +# "area": { +# "area_id": "1.1.1.1", +# "secondaries": false +# }, +# "process_id": "100" +# } +# ] +# } +# ], +# "name": "Ethernet1/1" +# }, +# { +# "address_family": [ +# { +# "afi": "ipv4", +# "authentication": { +# "enable": true, +# "key_chain": "test-1" +# }, +# "cost": 100, +# "message_digest_key": { +# "encryption": 3, +# "key": "abc01d272be25d29", +# "key_id": 10 +# } +# }, +# { +# "afi": "ipv6", +# "network": "broadcast", +# "shutdown": true +# } +# ], +# "name": "Ethernet1/2" +# }, +# { +# "name": "Ethernet1/3" +# }, +""" +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: + - interface Ethernet1/1 + - ip router ospf multi-area 11.11.11.11 + - ip router ospf 100 area 1.1.1.1 secondaries none + - no ipv6 router ospfv3 multi-area 16.10.10.10 + - ipv6 router ospfv3 200 area 2.2.2.2 + - ipv6 router ospfv3 200 multi-area 21.0.0.0 + - ipv6 router ospfv3 300 multi-area 50.50.50.50 + - interface Ethernet1/2 + - no ip ospf authentication key-chain test-1 + - ip ospf authentication +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospf_interfaces.ospf_interfaces import ( + Ospf_interfacesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospf_interfaces.ospf_interfaces import ( + Ospf_interfaces, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Ospf_interfacesArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Ospf_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py new file mode 100644 index 00000000..174640a1 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py @@ -0,0 +1,1984 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2020 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 nxos_ospfv2 +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_ospfv2 +short_description: OSPFv2 resource module +description: +- This module manages OSPFv2 configuration on devices running Cisco NX-OS. +version_added: 1.0.0 +notes: +- Tested against NX-OS 7.0(3)I5(1). +- Unsupported for Cisco MDS +- This module works with connection C(network_cli) and C(httpapi). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section "^router ospf .*"). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of OSPF process configuration. + type: dict + suboptions: + processes: + description: + - A list of OSPF instances' configurations. + type: list + elements: dict + suboptions: + areas: + description: + - Configure properties of OSPF Areas. + type: list + elements: dict + suboptions: + area_id: + description: + - The Area ID in IP Address format. + type: str + required: true + authentication: + description: + - Authentication settings for the Area. + type: dict + suboptions: + set: + description: + - Set authentication for the area. + type: bool + message_digest: + description: + - Use message-digest authentication. + type: bool + default_cost: + description: + - Specify the default cost for default summary LSA. + type: int + filter_list: + description: + - Filter prefixes between OSPF areas. + type: list + elements: dict + suboptions: + route_map: + description: + - The Route-map name. + type: str + required: true + direction: + description: + - The direction to apply the route map. + type: str + choices: [in, out] + required: true + nssa: + description: + - NSSA settings for the area. + type: dict + suboptions: + set: + description: + - Configure area as NSSA. + type: bool + default_information_originate: + description: + - Originate Type-7 default LSA into NSSA area. + type: bool + no_redistribution: + description: + - Do not send redistributed LSAs into NSSA area. + type: bool + no_summary: + description: + - Do not send summary LSAs into NSSA area. + type: bool + translate: + description: + - Translate LSA. + type: dict + suboptions: + type7: + description: + - Translate from Type 7 to Type 5. + type: dict + suboptions: + always: + description: + - Always translate LSAs + type: bool + never: + description: + - Never translate LSAs + type: bool + supress_fa: + description: + - Suppress forwarding address in translated LSAs. + type: bool + ranges: + description: + - Configure an address range for the area. + type: list + elements: dict + suboptions: + prefix: + description: + - IP in Prefix format (x.x.x.x/len) + type: str + required: true + cost: + description: + - Cost to use for the range. + type: int + not_advertise: + description: + - Suppress advertising the specified range. + type: bool + stub: + description: + - Settings for configuring the area as a stub. + type: dict + suboptions: + set: + description: + - Configure the area as a stub. + type: bool + no_summary: + description: + - Prevent ABR from sending summary LSAs into stub area. + type: bool + auto_cost: + description: + - Calculate OSPF cost according to bandwidth. + type: dict + suboptions: + reference_bandwidth: + description: + - Reference bandwidth used to assign OSPF cost. + type: int + required: true + unit: + description: + - Specify in which unit the reference bandwidth is specified. + type: str + required: true + choices: [Gbps, Mbps] + bfd: + description: + - Enable BFD on all OSPF interfaces. + type: bool + default_information: + description: + - Control distribution of default routes. + type: dict + suboptions: + originate: + description: + - Distribute a default route. + type: dict + suboptions: + set: + description: + - Enable distribution of default route. + type: bool + always: + description: + - Always advertise a default route. + type: bool + route_map: + description: + - Policy to control distribution of default routes + type: str + default_metric: + description: + - Specify default metric for redistributed routes. + type: int + distance: + description: + - Configure the OSPF administrative distance. + type: int + flush_routes: + description: + - Flush routes on a non-graceful controlled restart. + type: bool + graceful_restart: + description: + - Configure graceful restart. + type: dict + suboptions: + set: + description: + - Enable graceful-restart. + type: bool + grace_period: + description: + - Configure maximum interval to restart gracefully. + type: int + helper_disable: + description: + - Enable/Disable helper mode. + type: bool + isolate: + description: + - Isolate this router from OSPF perspective. + type: bool + log_adjacency_changes: + description: + - Log changes in adjacency state. + type: dict + suboptions: + log: + description: + - Enable/disable logging changes in adjacency state. + type: bool + detail: + description: + - Notify all state changes. + type: bool + max_lsa: + description: + - Feature to limit the number of non-self-originated LSAs. + type: dict + suboptions: + max_non_self_generated_lsa: + description: + - Set the maximum number of non self-generated LSAs. + type: int + required: true + threshold: + description: + - Threshold value (%) at which to generate a warning message. + type: int + ignore_count: + description: + - Set count on how many times adjacencies can be suppressed. + type: int + ignore_time: + description: + - Set time during which all adjacencies are suppressed. + type: int + reset_time: + description: + - Set number of minutes after which ignore-count is reset to zero. + type: int + warning_only: + description: + - Log a warning message when limit is exceeded. + type: bool + max_metric: + description: + - Maximize the cost metric. + type: dict + suboptions: + router_lsa: + description: + - Router LSA configuration. + type: dict + suboptions: + set: + description: + - Set router-lsa attribute. + type: bool + external_lsa: + description: + - External LSA configuration. + type: dict + suboptions: + set: + description: + - Set external-lsa attribute. + type: bool + max_metric_value: + description: + - Set max metric value for external LSAs. + type: int + include_stub: + description: + - Advertise Max metric for Stub links as well. + type: bool + on_startup: + description: + - Effective only at startup. + type: dict + suboptions: + set: + description: + - Set on-startup attribute. + type: bool + wait_period: + description: + - Wait period in seconds after startup. + type: int + wait_for_bgp_asn: + description: + - ASN of BGP to wait for. + type: int + summary_lsa: + description: + - Summary LSAs configuration. + type: dict + suboptions: + set: + description: + - Set summary-lsa attribute. + type: bool + max_metric_value: + description: + - Max metric value for summary LSAs. + type: int + maximum_paths: + description: + - Maximum paths per destination. + type: int + mpls: + description: + - OSPF MPLS configuration settings. + type: dict + suboptions: + traffic_eng: + description: + - OSPF MPLS Traffic Engineering commands. + type: dict + suboptions: + areas: + description: + - List of Area IDs. + type: list + elements: dict + suboptions: + area_id: + description: + - Area Id in ip address format. + type: str + multicast_intact: + description: + - MPLS TE multicast support. + type: bool + router_id: + description: + - Router ID associated with TE. + type: str + name_lookup: + description: + - Display OSPF router ids as DNS names. + type: bool + passive_interface: + description: + - Suppress routing updates on the interface. + type: dict + suboptions: + default: + description: + - Interfaces passive by default. + type: bool + process_id: + description: + - The OSPF process tag. + type: str + required: true + redistribute: + description: + - Redistribute information from another routing protocol. + type: list + elements: dict + suboptions: + protocol: + description: + - The name of the protocol. + type: str + choices: [bgp, direct, eigrp, isis, lisp, ospf, rip, static] + required: true + id: + description: + - The identifier for the protocol specified. + type: str + route_map: + description: + - The route map policy to constrain redistribution. + type: str + required: true + rfc1583compatibility: + description: + - Configure 1583 compatibility for external path preferences. + type: bool + router_id: + description: + - Set OSPF process router-id. + type: str + shutdown: + description: + - Shutdown the OSPF protocol instance. + type: bool + summary_address: + description: + - Configure route summarization for redistribution. + type: list + elements: dict + suboptions: + prefix: + description: + - IP prefix in format x.x.x.x/ml. + type: str + required: true + not_advertise: + description: + - Suppress advertising the specified summary. + type: bool + tag: + description: + - A 32-bit tag value. + type: int + table_map: + description: + - Policy for filtering/modifying OSPF routes before sending them to RIB. + type: dict + suboptions: + name: + description: + - The Route Map name. + type: str + required: true + filter: + description: + - Block the OSPF routes from being sent to RIB. + type: bool + timers: + description: + - Configure timer related constants. + type: dict + suboptions: + lsa_arrival: + description: + - Mimimum interval between arrival of a LSA. + type: int + lsa_group_pacing: + description: + - LSA group refresh/maxage interval. + type: int + throttle: + description: + - Configure throttle related constants. + type: dict + suboptions: + lsa: + description: + - Set rate-limiting for LSA generation. + type: dict + suboptions: + start_interval: + description: + - The start interval. + type: int + hold_interval: + description: + - The hold interval. + type: int + max_interval: + description: + - The max interval. + type: int + spf: + description: + - Set OSPF SPF timers. + type: dict + suboptions: + initial_spf_delay: + description: + - Initial SPF schedule delay in milliseconds. + type: int + min_hold_time: + description: + - Minimum hold time between SPF calculations. + type: int + max_wait_time: + description: + - Maximum wait time between SPF calculations. + type: int + vrfs: + description: + - Configure VRF specific OSPF settings. + type: list + elements: dict + suboptions: + areas: + description: + - Configure properties of OSPF Areas. + type: list + elements: dict + suboptions: + area_id: + description: + - The Area ID in IP Address format. + type: str + required: true + authentication: + description: + - Authentication settings for the Area. + type: dict + suboptions: + set: + description: + - Set authentication for the area. + type: bool + message_digest: + description: + - Use message-digest authentication. + type: bool + default_cost: + description: + - Specify the default cost for default summary LSA. + type: int + filter_list: + description: + - Filter prefixes between OSPF areas. + type: list + elements: dict + suboptions: + route_map: + description: + - The Route-map name. + type: str + required: true + direction: + description: + - The direction to apply the route map. + type: str + choices: [in, out] + required: true + nssa: + description: + - NSSA settings for the area. + type: dict + suboptions: + set: + description: + - Configure area as NSSA. + type: bool + default_information_originate: + description: + - Originate Type-7 default LSA into NSSA area. + type: bool + no_redistribution: + description: + - Do not send redistributed LSAs into NSSA area. + type: bool + no_summary: + description: + - Do not send summary LSAs into NSSA area. + type: bool + translate: + description: + - Translate LSA. + type: dict + suboptions: + type7: + description: + - Translate from Type 7 to Type 5. + type: dict + suboptions: + always: + description: + - Always translate LSAs + type: bool + never: + description: + - Never translate LSAs + type: bool + supress_fa: + description: + - Suppress forwarding address in translated LSAs. + type: bool + ranges: + description: + - Configure an address range for the area. + type: list + elements: dict + suboptions: + prefix: + description: + - IP in Prefix format (x.x.x.x/len) + type: str + required: true + cost: + description: + - Cost to use for the range. + type: int + not_advertise: + description: + - Suppress advertising the specified range. + type: bool + stub: + description: + - Settings for configuring the area as a stub. + type: dict + suboptions: + set: + description: + - Configure the area as a stub. + type: bool + no_summary: + description: + - Prevent ABR from sending summary LSAs into stub area. + type: bool + auto_cost: + description: + - Calculate OSPF cost according to bandwidth. + type: dict + suboptions: + reference_bandwidth: + description: + - Reference bandwidth used to assign OSPF cost. + type: int + required: true + unit: + description: + - Specify in which unit the reference bandwidth is specified. + type: str + required: True + choices: [Gbps, Mbps] + bfd: + description: + - Enable BFD on all OSPF interfaces. + type: bool + default_information: + description: + - Control distribution of default routes. + type: dict + suboptions: + originate: + description: + - Distribute a default route. + type: dict + suboptions: + set: + description: + - Enable distribution of default route. + type: bool + always: + description: + - Always advertise a default route. + type: bool + route_map: + description: + - Policy to control distribution of default routes + type: str + default_metric: + description: + - Specify default metric for redistributed routes. + type: int + distance: + description: + - Configure the OSPF administrative distance. + type: int + down_bit_ignore: + description: + - Configure a PE router to ignore the DN bit for network summary, + external and NSSA external LSA. + type: bool + capability: + description: + - OSPF capability settings. + type: dict + suboptions: + vrf_lite: + description: + - Enable VRF-lite capability settings. + type: dict + suboptions: + set: + description: + - Enable VRF-lite support. + type: bool + evpn: + description: + - Ethernet VPN. + type: bool + graceful_restart: + description: + - Configure graceful restart. + type: dict + suboptions: + set: + description: + - Enable graceful-restart. + type: bool + grace_period: + description: + - Configure maximum interval to restart gracefully. + type: int + helper_disable: + description: + - Enable/Disable helper mode. + type: bool + log_adjacency_changes: + description: + - Log changes in adjacency state. + type: dict + suboptions: + log: + description: + - Enable/disable logging changes in adjacency state. + type: bool + detail: + description: + - Notify all state changes. + type: bool + max_lsa: + description: + - Feature to limit the number of non-self-originated LSAs. + type: dict + suboptions: + max_non_self_generated_lsa: + description: + - Set the maximum number of non self-generated LSAs. + type: int + required: true + threshold: + description: + - Threshold value (%) at which to generate a warning message. + type: int + ignore_count: + description: + - Set count on how many times adjacencies can be suppressed. + type: int + ignore_time: + description: + - Set time during which all adjacencies are suppressed. + type: int + reset_time: + description: + - Set number of minutes after which ignore-count is reset to zero. + type: int + warning_only: + description: + - Log a warning message when limit is exceeded. + type: bool + max_metric: + description: + - Maximize the cost metric. + type: dict + suboptions: + router_lsa: + description: + - Router LSA configuration. + type: dict + suboptions: + set: + description: + - Set router-lsa attribute. + type: bool + external_lsa: + description: + - External LSA configuration. + type: dict + suboptions: + set: + description: + - Set external-lsa attribute. + type: bool + max_metric_value: + description: + - Set max metric value for external LSAs. + type: int + include_stub: + description: + - Advertise Max metric for Stub links as well. + type: bool + on_startup: + description: + - Effective only at startup. + type: dict + suboptions: + set: + description: + - Set on-startup attribute. + type: bool + wait_period: + description: + - Wait period in seconds after startup. + type: int + wait_for_bgp_asn: + description: + - ASN of BGP to wait for. + type: int + summary_lsa: + description: + - Summary LSAs configuration. + type: dict + suboptions: + set: + description: + - Set summary-lsa attribute. + type: bool + max_metric_value: + description: + - Max metric value for summary LSAs. + type: int + maximum_paths: + description: + - Maximum paths per destination. + type: int + name_lookup: + description: + - Display OSPF router ids as DNS names. + type: bool + passive_interface: + description: + - Suppress routing updates on the interface. + type: dict + suboptions: + default: + description: + - Interfaces passive by default. + type: bool + redistribute: + description: + - Redistribute information from another routing protocol. + type: list + elements: dict + suboptions: + protocol: + description: + - The name of the protocol. + type: str + choices: [bgp, direct, eigrp, isis, lisp, ospf, rip, static] + required: true + id: + description: + - The identifier for the protocol specified. + type: str + route_map: + description: + - The route map policy to constrain redistribution. + type: str + required: true + rfc1583compatibility: + description: + - Configure 1583 compatibility for external path preferences. + type: bool + router_id: + description: + - Set OSPF process router-id. + type: str + shutdown: + description: + - Shutdown the OSPF protocol instance. + type: bool + summary_address: + description: + - Configure route summarization for redistribution. + type: list + elements: dict + suboptions: + prefix: + description: + - IP prefix in format x.x.x.x/ml. + type: str + required: true + not_advertise: + description: + - Suppress advertising the specified summary. + type: bool + tag: + description: + - A 32-bit tag value. + type: int + table_map: + description: + - Policy for filtering/modifying OSPF routes before sending them to + RIB. + type: dict + suboptions: + name: + description: + - The Route Map name. + type: str + required: true + filter: + description: + - Block the OSPF routes from being sent to RIB. + type: bool + timers: + description: + - Configure timer related constants. + type: dict + suboptions: + lsa_arrival: + description: + - Mimimum interval between arrival of a LSA. + type: int + lsa_group_pacing: + description: + - LSA group refresh/maxage interval. + type: int + throttle: + description: + - Configure throttle related constants. + type: dict + suboptions: + lsa: + description: + - Set rate-limiting for LSA generation. + type: dict + suboptions: + start_interval: + description: + - The start interval. + type: int + hold_interval: + description: + - The hold interval. + type: int + max_interval: + description: + - The max interval. + type: int + spf: + description: + - Set OSPF SPF timers. + type: dict + suboptions: + initial_spf_delay: + description: + - Initial SPF schedule delay in milliseconds. + type: int + min_hold_time: + description: + - Minimum hold time between SPF calculations. + type: int + max_wait_time: + description: + - Maximum wait time between SPF calculations. + type: int + vrf: + description: + - Name/Identifier of the VRF. + type: str + required: true + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - parsed + - rendered + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# nxos-9k-rdo# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_ospfv2: + config: + processes: + - process_id: 100 + router_id: 203.0.113.20 + - process_id: 102 + router_id: 198.51.100.1 + areas: + - area_id: 0.0.0.100 + filter_list: + - route_map: rmap_1 + direction: in + - route_map: rmap_2 + direction: out + ranges: + - prefix: 198.51.100.64/27 + not_advertise: true + - prefix: 198.51.100.96/27 + cost: 120 + - area_id: 0.0.0.101 + authentication: + message_digest: true + redistribute: + - protocol: eigrp + id: 120 + route_map: rmap_1 + - protocol: direct + route_map: ospf102-direct-connect + vrfs: + - vrf: zone1 + router_id: 198.51.100.129 + redistribute: + - protocol: static + route_map: zone1-static-connect + summary_address: + - prefix: 198.51.100.128/27 + tag: 121 + - prefix: 198.51.100.160/27 + areas: + - area_id: 0.0.0.102 + nssa: + default_information_originate: true + no_summary: true + - area_id: 0.0.0.103 + nssa: + no_summary: true + translate: + type7: + always: true + - vrf: zone2 + auto_cost: + reference_bandwidth: 45 + unit: Gbps + state: merged + +# Task output +# ------------- +# before: {} +# +# commands: +# - router ospf 102 +# - router-id 198.51.100.1 +# - redistribute eigrp 120 route-map rmap_1 +# - redistribute direct route-map ospf102-direct-connect +# - area 0.0.0.100 filter-list route-map rmap_1 in +# - area 0.0.0.100 filter-list route-map rmap_2 out +# - area 0.0.0.100 range 198.51.100.64/27 not-advertise +# - area 0.0.0.100 range 198.51.100.96/27 cost 120 +# - area 0.0.0.101 authentication message-digest +# - vrf zone1 +# - router-id 198.51.100.129 +# - summary-address 198.51.100.128/27 tag 121 +# - summary-address 198.51.100.160/27 +# - redistribute static route-map zone1-static-connect +# - area 0.0.0.102 nssa no-summary default-information-originate +# - area 0.0.0.103 nssa no-summary +# - area 0.0.0.103 nssa translate type7 always +# - vrf zone2 +# - auto-cost reference-bandwidth 45 Gbps +# - router ospf 100 +# - router-id 203.0.113.20 +# +# after: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 198.51.100.64/27 +# - cost: 120 +# prefix: 198.51.100.96/27 +# - area_id: 0.0.0.101 +# authentication: +# message_digest: true +# process_id: "102" +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# redistribute: +# - protocol: static +# route_map: zone1-static-connect +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospf 100 +# router-id 203.0.113.20 +# router ospf 102 +# router-id 198.51.100.1 +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 198.51.100.64/27 not-advertise +# area 0.0.0.100 range 198.51.100.96/27 cost 120 +# area 0.0.0.101 authentication message-digest +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# redistribute static route-map zone1-static-connect +# summary-address 198.51.100.128/27 tag 121 +# summary-address 198.51.100.160/27 +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +# Using replaced + +# Before state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospf 100 +# router-id 203.0.113.20 +# router ospf 102 +# router-id 198.51.100.1 +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 198.51.100.64/27 not-advertise +# area 0.0.0.100 range 198.51.100.96/27 cost 120 +# area 0.0.0.101 authentication message-digest +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# redistribute static route-map zone1-static-connect +# summary-address 198.51.100.128/27 tag 121 +# summary-address 198.51.100.160/27 +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +- name: Replace device configurations of listed OSPF processes with provided configurations + cisco.nxos.nxos_ospfv2: + config: + processes: + - process_id: 102 + router_id: 198.51.100.1 + areas: + - area_id: 0.0.0.100 + filter_list: + - route_map: rmap_8 + direction: in + ranges: + - prefix: 198.51.100.64/27 + not_advertise: true + - area_id: 0.0.0.101 + stub: + no_summary: true + redistribute: + - protocol: eigrp + id: 130 + route_map: rmap_1 + - protocol: direct + route_map: ospf102-direct-connect + vrfs: + - vrf: zone1 + router_id: 198.51.100.129 + redistribute: + - protocol: bgp + id: 65563 + route_map: zone1-bgp-connect + areas: + - area_id: 0.0.0.102 + nssa: + default_information_originate: true + no_summary: true + state: replaced + +# Task output +# ------------- +# before: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 198.51.100.64/27 +# - cost: 120 +# prefix: 198.51.100.96/27 +# - area_id: 0.0.0.101 +# authentication: +# message_digest: true +# process_id: "102" +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# redistribute: +# - protocol: static +# route_map: zone1-static-connect +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +# commands: +# - router ospf 102 +# - redistribute eigrp 130 route-map rmap_1 +# - no redistribute eigrp 120 route-map rmap_1 +# - area 0.0.0.100 filter-list route-map rmap_8 in +# - no area 0.0.0.100 filter-list route-map rmap_2 out +# - no area 0.0.0.100 range 198.51.100.96/27 +# - no area 0.0.0.101 authentication +# - area 0.0.0.101 stub no-summary +# - vrf zone1 +# - no summary-address 198.51.100.128/27 tag 121 +# - no summary-address 198.51.100.160/27 +# - redistribute bgp 65563 route-map zone1-bgp-connect +# - no redistribute static route-map zone1-static-connect +# - no area 0.0.0.103 nssa +# - no area 0.0.0.103 nssa translate type7 always +# - no vrf zone2 +# +# after: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - areas: +# - area_id: 0.0.0.101 +# stub: +# no_summary: true +# - area_id: 0.0.0.100 +# filter_list: +# - direction: in +# route_map: rmap_8 +# ranges: +# - not_advertise: true +# prefix: 198.51.100.64/27 +# process_id: "102" +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "130" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# redistribute: +# - id: "65563" +# protocol: bgp +# route_map: zone1-bgp-connect +# router_id: 198.51.100.129 +# vrf: zone1 + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospf 100 +# router-id 203.0.113.20 +# router ospf 102 +# router-id 198.51.100.1 +# area 0.0.0.101 stub no-summary +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 130 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_8 in +# area 0.0.0.100 range 198.51.100.64/27 not-advertise +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# redistribute bgp 65563 route-map zone1-bgp-connect + +# Using overridden + +# Before state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospf 100 +# router-id 203.0.113.20 +# router ospf 102 +# router-id 198.51.100.1 +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 198.51.100.64/27 not-advertise +# area 0.0.0.100 range 198.51.100.96/27 cost 120 +# area 0.0.0.101 authentication message-digest +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# redistribute static route-map zone1-static-connect +# summary-address 198.51.100.128/27 tag 121 +# summary-address 198.51.100.160/27 +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +- name: Override all OSPF configuration with provided configuration + cisco.nxos.nxos_ospfv2: + config: + processes: + - process_id: 104 + router_id: 203.0.113.20 + - process_id: 102 + router_id: 198.51.100.1 + shutdown: true + state: overridden + +# Task output +# ------------- +# before: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 198.51.100.64/27 +# - cost: 120 +# prefix: 198.51.100.96/27 +# - area_id: 0.0.0.101 +# authentication: +# message_digest: true +# process_id: "102" +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# redistribute: +# - protocol: static +# route_map: zone1-static-connect +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +# commands: +# - no router ospf 100 +# - router ospf 104 +# - router-id 203.0.113.20 +# - router ospf 102 +# - shutdown +# - no redistribute direct route-map ospf102-direct-connect +# - no redistribute eigrp 120 route-map rmap_1 +# - no area 0.0.0.100 filter-list route-map rmap_2 out +# - no area 0.0.0.100 filter-list route-map rmap_1 in +# - no area 0.0.0.100 range 198.51.100.64/27 +# - no area 0.0.0.100 range 198.51.100.96/27 +# - no area 0.0.0.101 authentication +# - no vrf zone1 +# - no vrf zone2 +# +# after: +# processes: +# - process_id: "102" +# router_id: 198.51.100.1 +# shutdown: true +# - process_id: "104" +# router_id: 203.0.113.20 + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospf 102 +# router-id 198.51.100.1 +# shutdown +# router ospf 104 +# router-id 203.0.113.20 + +# Using deleted to delete a single OSPF process + +# Before state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospf 100 +# router-id 203.0.113.20 +# router ospf 102 +# router-id 198.51.100.1 +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 198.51.100.64/27 not-advertise +# area 0.0.0.100 range 198.51.100.96/27 cost 120 +# area 0.0.0.101 authentication message-digest +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# redistribute static route-map zone1-static-connect +# summary-address 198.51.100.128/27 tag 121 +# summary-address 198.51.100.160/27 +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +- name: Delete a single OSPF process + cisco.nxos.nxos_ospfv2: + config: + processes: + - process_id: 102 + state: deleted + +# Task output +# ------------- +# before: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 198.51.100.64/27 +# - cost: 120 +# prefix: 198.51.100.96/27 +# - area_id: 0.0.0.101 +# authentication: +# message_digest: true +# process_id: "102" +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# redistribute: +# - protocol: static +# route_map: zone1-static-connect +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +# commands: +# - no router ospf 102 +# +# after: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospf 100 +# router-id 203.0.113.20 + +# Using deleted all OSPF processes from the device + +# Before state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospf 100 +# router-id 203.0.113.20 +# router ospf 102 +# router-id 198.51.100.1 +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 198.51.100.64/27 not-advertise +# area 0.0.0.100 range 198.51.100.96/27 cost 120 +# area 0.0.0.101 authentication message-digest +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# redistribute static route-map zone1-static-connect +# summary-address 198.51.100.128/27 tag 121 +# summary-address 198.51.100.160/27 +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +- name: Delete all OSPF processes from the device + cisco.nxos.nxos_ospfv2: + state: deleted + +# Task output +# ------------- +# before: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 198.51.100.64/27 +# - cost: 120 +# prefix: 198.51.100.96/27 +# - area_id: 0.0.0.101 +# authentication: +# message_digest: true +# process_id: "102" +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# redistribute: +# - protocol: static +# route_map: zone1-static-connect +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +# commands: +# - no router ospf 100 +# - no router ospf 102 +# +# after: {} + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# nxos-9k-rdo# + +# Using rendered + +- name: Render platform specific configuration lines (without connecting to the device) + cisco.nxos.nxos_ospfv2: + config: + processes: + - process_id: 100 + router_id: 203.0.113.20 + - process_id: 102 + router_id: 198.51.100.1 + areas: + - area_id: 0.0.0.100 + filter_list: + - route_map: rmap_1 + direction: in + - route_map: rmap_2 + direction: out + ranges: + - prefix: 198.51.100.64/27 + not_advertise: true + - prefix: 198.51.100.96/27 + cost: 120 + - area_id: 0.0.0.101 + authentication: + message_digest: true + redistribute: + - protocol: eigrp + id: 120 + route_map: rmap_1 + - protocol: direct + route_map: ospf102-direct-connect + vrfs: + - vrf: zone1 + router_id: 198.51.100.129 + redistribute: + - protocol: static + route_map: zone1-static-connect + summary_address: + - prefix: 198.51.100.128/27 + tag: 121 + - prefix: 198.51.100.160/27 + areas: + - area_id: 0.0.0.102 + nssa: + default_information_originate: true + no_summary: true + - area_id: 0.0.0.103 + nssa: + no_summary: true + translate: + type7: + always: true + - vrf: zone2 + auto_cost: + reference_bandwidth: 45 + unit: Gbps + state: rendered + +# Task Output (redacted) +# ----------------------- +# rendered: +# - router ospf 100 +# - router-id 203.0.113.20 +# - router ospf 102 +# - router-id 198.51.100.1 +# - redistribute eigrp 120 route-map rmap_1 +# - redistribute direct route-map ospf102-direct-connect +# - area 0.0.0.100 filter-list route-map rmap_1 in +# - area 0.0.0.100 filter-list route-map rmap_2 out +# - area 0.0.0.100 range 198.51.100.64/27 not-advertise +# - area 0.0.0.100 range 198.51.100.96/27 cost 120 +# - area 0.0.0.101 authentication message-digest +# - vrf zone1 +# - router-id 198.51.100.129 +# - summary-address 198.51.100.128/27 tag 121 +# - summary-address 198.51.100.160/27 +# - redistribute static route-map zone1-static-connect +# - area 0.0.0.102 nssa no-summary default-information-originate +# - area 0.0.0.103 nssa no-summary +# - area 0.0.0.103 nssa translate type7 always +# - vrf zone2 +# - auto-cost reference-bandwidth 45 Gbps + +# Using parsed + +# parsed.cfg +# ------------ +# router ospf 100 +# router-id 192.0.100.1 +# area 0.0.0.101 nssa no-summary no-redistribution +# area 0.0.0.102 stub no-summary +# redistribute direct route-map ospf-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 192.0.2.0/24 not-advertise +# area 0.0.0.100 range 192.0.3.0/24 cost 120 +# area 0.0.0.100 authentication message-digest +# vrf zone1 +# router-id 192.0.100.2 +# area 0.0.100.1 nssa no-summary no-redistribution +# redistribute static route-map zone1-direct-connect +# summary-address 10.0.0.0/24 tag 120 +# summary-address 11.0.0.0/24 not-advertise +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps +# down-bit-ignore +# capability vrf-lite evpn +# shutdown +# router ospf 102 +# router-id 198.54.100.1 +# shutdown +# vrf zone2 +# summary-address 192.0.8.0/24 tag 120 +# vrf zone4 +# shutdown + +- name: Parse externally provided OSPFv2 config + cisco.nxos.nxos_ospfv2: + running_config: "{{ lookup('file', 'ospfv2.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# processes: +# - process_id: "100" +# areas: +# - area_id: 0.0.0.101 +# nssa: +# no_redistribution: true +# no_summary: true +# - area_id: 0.0.0.102 +# stub: +# no_summary: true +# - area_id: 0.0.0.100 +# authentication: +# message_digest: true +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 192.0.2.0/24 +# - cost: 120 +# prefix: 192.0.3.0/24 +# redistribute: +# - protocol: direct +# route_map: ospf-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 192.0.100.1 +# vrfs: +# - vrf: zone1 +# areas: +# - area_id: 0.0.100.1 +# nssa: +# no_redistribution: true +# no_summary: true +# redistribute: +# - protocol: static +# route_map: zone1-direct-connect +# router_id: 192.0.100.2 +# summary_address: +# - prefix: 10.0.0.0/24 +# tag: 120 +# - not_advertise: true +# prefix: 11.0.0.0/24 +# - vrf: zone2 +# auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# capability: +# vrf_lite: +# evpn: true +# down_bit_ignore: true +# shutdown: true +# - process_id: "102" +# router_id: 198.54.100.1 +# shutdown: true +# vrfs: +# - vrf: zone2 +# summary_address: +# - prefix: 192.0.8.0/24 +# tag: 120 +# - vrf: zone4 +# shutdown: true + +# Using gathered + +- name: Gather OSPFv2 facts using gathered + cisco.nxos.nxos_ospfv2: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# processes: +# - process_id: "102" +# areas: +# - area_id: 0.0.0.101 +# stub: +# no_summary: true +# - area_id: 0.0.0.100 +# filter_list: +# - direction: in +# route_map: rmap_8 +# ranges: +# - not_advertise: true +# prefix: 198.51.100.64/27 +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "130" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 198.51.100.1 +# vrfs: +# - vrf: zone1 +# areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# redistribute: +# - id: "65563" +# protocol: bgp +# route_map: zone1-bgp-connect +# router_id: 198.51.100.129 +# +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: + - "router ospf 102" + - "router-id 198.54.100.1" + - "router ospf 100" + - "router-id 192.0.100.1" + - "redistribute eigrp 120 route-map rmap_1" + - "redistribute direct route-map ospf-direct-connect" + - "area 0.0.0.100 filter-list route-map rmap_1 in" + - "area 0.0.0.100 filter-list route-map rmap_2 out" + - "area 0.0.0.100 range 192.0.2.0/24 not-advertise" + - "area 0.0.0.100 range 192.0.3.0/24 cost 120" + - "vrf zone1" + - "router-id 192.0.100.2" + - "summary-address 10.0.0.0/24 tag 121" + - "summary-address 11.0.0.0/24" + - "redistribute static route-map zone1-direct-connect" + - "vrf zone2" + - "auto-cost reference-bandwidth 45 Gbps" + - "capability vrf-lite evpn" +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv2.ospfv2 import ( + Ospfv2Args, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospfv2.ospfv2 import ( + Ospfv2, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + module = AnsibleModule( + argument_spec=Ospfv2Args.argument_spec, + required_if=required_if, + supports_check_mode=True, + ) + + result = Ospfv2(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py new file mode 100644 index 00000000..27d9ff65 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py @@ -0,0 +1,1702 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2020 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 nxos_ospfv3 +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_ospfv3 +short_description: OSPFv3 resource module +description: +- This module manages OSPFv3 configuration on devices running Cisco NX-OS. +version_added: 1.2.0 +notes: +- Tested against NX-OS 7.0(3)I5(1). +- Unsupported for Cisco MDS +- This module works with connection C(network_cli) and C(httpapi). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section "^router ospfv3"). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of OSPFv3 process configuration. + type: dict + suboptions: + processes: + description: + - A list of OSPFv3 instances' configurations. + type: list + elements: dict + suboptions: + address_family: + description: + - IPv6 unicast address-family OSPFv3 settings. + type: dict + suboptions: + afi: + description: + - Configure OSPFv3 settings under IPv6 address-family. + type: str + choices: ['ipv6'] + safi: + description: + - Configure OSPFv3 settings under IPv6 unicast address-family. + type: str + choices: ['unicast'] + areas: + description: + - Configure properties of OSPF Areas under address-family. + type: list + elements: dict + suboptions: + area_id: + description: + - The Area ID in IP Address format. + type: str + required: True + default_cost: + description: + - Specify the default cost. + type: int + filter_list: + description: + - Filter prefixes between OSPF areas. + type: list + elements: dict + suboptions: + route_map: + description: + - The Route-map name. + type: str + required: True + direction: + description: + - The direction to apply the route map. + type: str + choices: [in, out] + required: True + ranges: + description: + - Configure an address range for the area. + type: list + elements: dict + suboptions: + prefix: + description: + - IP in Prefix format (x.x.x.x/len) + type: str + required: True + cost: + description: + - Cost to use for the range. + type: int + not_advertise: + description: + - Suppress advertising the specified range. + type: bool + default_information: + description: + - Control distribution of default routes. + type: dict + suboptions: + originate: + description: + - Distribute a default route. + type: dict + suboptions: + set: + description: + - Enable distribution of default route. + type: bool + always: + description: + - Always advertise a default route. + type: bool + route_map: + description: + - Policy to control distribution of default routes + type: str + distance: + description: + - Configure the OSPF administrative distance. + type: int + maximum_paths: + description: + - Maximum paths per destination. + type: int + redistribute: + description: + - Redistribute information from another routing protocol. + type: list + elements: dict + suboptions: + protocol: + description: + - The name of the protocol. + type: str + choices: [bgp, direct, eigrp, isis, lisp, ospfv3, rip, static] + required: True + id: + description: + - The identifier for the protocol specified. + type: str + route_map: + description: + - The route map policy to constrain redistribution. + type: str + required: True + summary_address: + description: + - Configure route summarization for redistribution. + type: list + elements: dict + suboptions: + prefix: + description: + - IPv6 prefix format 'xxxx:xxxx/ml', 'xxxx:xxxx::/ml' or 'xxxx::xx/128' + type: str + required: True + not_advertise: + description: + - Suppress advertising the specified summary. + type: bool + tag: + description: + - A 32-bit tag value. + type: int + table_map: + description: + - Policy for filtering/modifying OSPF routes before sending them to + RIB. + type: dict + suboptions: + name: + description: + - The Route Map name. + type: str + required: True + filter: + description: + - Block the OSPF routes from being sent to RIB. + type: bool + timers: + description: + - Configure timer related constants. + type: dict + suboptions: + throttle: + description: + - Configure throttle related constants. + type: dict + suboptions: + spf: + description: + - Set OSPF SPF timers. + type: dict + suboptions: + initial_spf_delay: + description: + - Initial SPF schedule delay in milliseconds. + type: int + min_hold_time: + description: + - Minimum hold time between SPF calculations. + type: int + max_wait_time: + description: + - Maximum wait time between SPF calculations. + type: int + areas: + description: + - Configure properties of OSPF Areas. + type: list + elements: dict + suboptions: + area_id: + description: + - The Area ID in IP Address format. + type: str + required: True + nssa: + description: + - NSSA settings for the area. + type: dict + suboptions: + set: + description: + - Configure area as NSSA. + type: bool + default_information_originate: + description: + - Originate Type-7 default LSA into NSSA area. + type: bool + no_redistribution: + description: + - Do not send redistributed LSAs into NSSA area. + type: bool + no_summary: + description: + - Do not send summary LSAs into NSSA area. + type: bool + route_map: + description: + - Policy to control distribution of default route. + type: str + translate: + description: + - Translate LSA. + type: dict + suboptions: + type7: + description: + - Translate from Type 7 to Type 5. + type: dict + suboptions: + always: + description: + - Always translate LSAs + type: bool + never: + description: + - Never translate LSAs + type: bool + supress_fa: + description: + - Suppress forwarding address in translated LSAs. + type: bool + stub: + description: + - Settings for configuring the area as a stub. + type: dict + suboptions: + set: + description: + - Configure the area as a stub. + type: bool + no_summary: + description: + - Prevent ABR from sending summary LSAs into stub area. + type: bool + auto_cost: + description: + - Calculate OSPF cost according to bandwidth. + type: dict + suboptions: + reference_bandwidth: + description: + - Reference bandwidth used to assign OSPF cost. + type: int + required: True + unit: + description: + - Specify in which unit the reference bandwidth is specified. + type: str + required: True + choices: [Gbps, Mbps] + flush_routes: + description: + - Flush routes on a non-graceful controlled restart. + type: bool + graceful_restart: + description: + - Configure graceful restart. + type: dict + suboptions: + set: + description: + - Enable graceful-restart. + type: bool + grace_period: + description: + - Configure maximum interval to restart gracefully. + type: int + helper_disable: + description: + - Enable/Disable helper mode. + type: bool + planned_only: + description: + - Enable graceful restart only for a planned restart + type: bool + isolate: + description: + - Isolate this router from OSPF perspective. + type: bool + log_adjacency_changes: + description: + - Log changes in adjacency state. + type: dict + suboptions: + log: + description: + - Enable/disable logging changes in adjacency state. + type: bool + detail: + description: + - Notify all state changes. + type: bool + max_lsa: + description: + - Feature to limit the number of non-self-originated LSAs. + type: dict + suboptions: + max_non_self_generated_lsa: + description: + - Set the maximum number of non self-generated LSAs. + type: int + required: True + threshold: + description: + - Threshold value (%) at which to generate a warning message. + type: int + ignore_count: + description: + - Set count on how many times adjacencies can be suppressed. + type: int + ignore_time: + description: + - Set time during which all adjacencies are suppressed. + type: int + reset_time: + description: + - Set number of minutes after which ignore-count is reset to zero. + type: int + warning_only: + description: + - Log a warning message when limit is exceeded. + type: bool + max_metric: + description: + - Maximize the cost metric. + type: dict + suboptions: + router_lsa: + description: + - Router LSA configuration. + type: dict + suboptions: + set: + description: + - Set router-lsa attribute. + type: bool + external_lsa: + description: + - External LSA configuration. + type: dict + suboptions: + set: + description: + - Set external-lsa attribute. + type: bool + max_metric_value: + description: + - Set max metric value for external LSAs. + type: int + stub_prefix_lsa: + description: + - Advertise Max metric for Stub links as well. + type: bool + on_startup: + description: + - Effective only at startup. + type: dict + suboptions: + set: + description: + - Set on-startup attribute. + type: bool + wait_period: + description: + - Wait period in seconds after startup. + type: int + wait_for_bgp_asn: + description: + - ASN of BGP to wait for. + type: int + inter_area_prefix_lsa: + description: + - Inter-area-prefix LSAs configuration. + type: dict + suboptions: + set: + description: + - Set summary-lsa attribute. + type: bool + max_metric_value: + description: + - Max metric value for summary LSAs. + type: int + name_lookup: + description: + - Display OSPF router ids as DNS names. + type: bool + passive_interface: + description: + - Suppress routing updates on the interface. + type: dict + suboptions: + default: + description: + - Interfaces passive by default. + type: bool + process_id: + description: + - The OSPF process tag. + type: str + required: True + router_id: + description: + - Set OSPF process router-id. + type: str + shutdown: + description: + - Shutdown the OSPF protocol instance. + type: bool + timers: + description: + - Configure timer related constants. + type: dict + suboptions: + lsa_arrival: + description: + - Mimimum interval between arrival of a LSA. + type: int + lsa_group_pacing: + description: + - LSA group refresh/maxage interval. + type: int + throttle: + description: + - Configure throttle related constants. + type: dict + suboptions: + lsa: + description: + - Set rate-limiting for LSA generation. + type: dict + suboptions: + start_interval: + description: + - The start interval. + type: int + hold_interval: + description: + - The hold interval. + type: int + max_interval: + description: + - The max interval. + type: int + vrfs: + description: + - Configure VRF specific OSPF settings. + type: list + elements: dict + suboptions: + areas: + description: + - Configure properties of OSPF Areas. + type: list + elements: dict + suboptions: + area_id: + description: + - The Area ID in IP Address format. + type: str + required: True + nssa: + description: + - NSSA settings for the area. + type: dict + suboptions: + set: + description: + - Configure area as NSSA. + type: bool + default_information_originate: + description: + - Originate Type-7 default LSA into NSSA area. + type: bool + no_redistribution: + description: + - Do not send redistributed LSAs into NSSA area. + type: bool + no_summary: + description: + - Do not send summary LSAs into NSSA area. + type: bool + route_map: + description: + - Policy to control distribution of default route. + type: str + translate: + description: + - Translate LSA. + type: dict + suboptions: + type7: + description: + - Translate from Type 7 to Type 5. + type: dict + suboptions: + always: + description: + - Always translate LSAs + type: bool + never: + description: + - Never translate LSAs + type: bool + supress_fa: + description: + - Suppress forwarding address in translated LSAs. + type: bool + stub: + description: + - Settings for configuring the area as a stub. + type: dict + suboptions: + set: + description: + - Configure the area as a stub. + type: bool + no_summary: + description: + - Prevent ABR from sending summary LSAs into stub area. + type: bool + auto_cost: + description: + - Calculate OSPF cost according to bandwidth. + type: dict + suboptions: + reference_bandwidth: + description: + - Reference bandwidth used to assign OSPF cost. + type: int + required: True + unit: + description: + - Specify in which unit the reference bandwidth is specified. + type: str + required: True + choices: [Gbps, Mbps] + graceful_restart: + description: + - Configure graceful restart. + type: dict + suboptions: + set: + description: + - Enable graceful-restart. + type: bool + grace_period: + description: + - Configure maximum interval to restart gracefully. + type: int + helper_disable: + description: + - Enable/Disable helper mode. + type: bool + planned_only: + description: + - Enable graceful restart only for a planned restart + type: bool + log_adjacency_changes: + description: + - Log changes in adjacency state. + type: dict + suboptions: + log: + description: + - Enable/disable logging changes in adjacency state. + type: bool + detail: + description: + - Notify all state changes. + type: bool + max_lsa: + description: + - Feature to limit the number of non-self-originated LSAs. + type: dict + suboptions: + max_non_self_generated_lsa: + description: + - Set the maximum number of non self-generated LSAs. + type: int + required: True + threshold: + description: + - Threshold value (%) at which to generate a warning message. + type: int + ignore_count: + description: + - Set count on how many times adjacencies can be suppressed. + type: int + ignore_time: + description: + - Set time during which all adjacencies are suppressed. + type: int + reset_time: + description: + - Set number of minutes after which ignore-count is reset to zero. + type: int + warning_only: + description: + - Log a warning message when limit is exceeded. + type: bool + max_metric: + description: + - Maximize the cost metric. + type: dict + suboptions: + router_lsa: + description: + - Router LSA configuration. + type: dict + suboptions: + set: + description: + - Set router-lsa attribute. + type: bool + external_lsa: + description: + - External LSA configuration. + type: dict + suboptions: + set: + description: + - Set external-lsa attribute. + type: bool + max_metric_value: + description: + - Set max metric value for external LSAs. + type: int + stub_prefix_lsa: + description: + - Advertise Max metric for Stub links as well. + type: bool + on_startup: + description: + - Effective only at startup. + type: dict + suboptions: + set: + description: + - Set on-startup attribute. + type: bool + wait_period: + description: + - Wait period in seconds after startup. + type: int + wait_for_bgp_asn: + description: + - ASN of BGP to wait for. + type: int + inter_area_prefix_lsa: + description: + - Inter-area-prefix LSAs configuration. + type: dict + suboptions: + set: + description: + - Set summary-lsa attribute. + type: bool + max_metric_value: + description: + - Max metric value for summary LSAs. + type: int + name_lookup: + description: + - Display OSPF router ids as DNS names. + type: bool + passive_interface: + description: + - Suppress routing updates on the interface. + type: dict + suboptions: + default: + description: + - Interfaces passive by default. + type: bool + router_id: + description: + - Set OSPF process router-id. + type: str + shutdown: + description: + - Shutdown the OSPF protocol instance. + type: bool + timers: + description: + - Configure timer related constants. + type: dict + suboptions: + lsa_arrival: + description: + - Mimimum interval between arrival of a LSA. + type: int + lsa_group_pacing: + description: + - LSA group refresh/maxage interval. + type: int + throttle: + description: + - Configure throttle related constants. + type: dict + suboptions: + lsa: + description: + - Set rate-limiting for LSA generation. + type: dict + suboptions: + start_interval: + description: + - The start interval. + type: int + hold_interval: + description: + - The hold interval. + type: int + max_interval: + description: + - The max interval. + type: int + vrf: + description: + - Name/Identifier of the VRF. + type: str + required: True + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - parsed + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# nxos-9k-rdo# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_ospfv3: + config: + processes: + - process_id: 100 + router_id: 203.0.113.20 + - process_id: 102 + router_id: 198.51.100.1 + address_family: + afi: ipv6 + safi: unicast + areas: + - area_id: 0.0.0.100 + filter_list: + - route_map: rmap_1 + direction: in + - route_map: rmap_2 + direction: out + ranges: + - prefix: 2001:db2::/32 + not_advertise: true + - prefix: 2001:db3::/32 + cost: 120 + redistribute: + - protocol: eigrp + id: 120 + route_map: rmap_1 + - protocol: direct + route_map: ospf102-direct-connect + vrfs: + - vrf: zone1 + router_id: 198.51.100.129 + areas: + - area_id: 0.0.0.102 + nssa: + default_information_originate: true + no_summary: true + - area_id: 0.0.0.103 + nssa: + no_summary: true + translate: + type7: + always: true + - vrf: zone2 + auto_cost: + reference_bandwidth: 45 + unit: Gbps + state: merged + +# Task output +# ------------- +# before: {} +# +# commands: +# - router ospf 102 +# - router-id 198.51.100.1 +# - address-family ipv6 unicast +# - redistribute eigrp 120 route-map rmap_1 +# - redistribute direct route-map ospf102-direct-connect +# - area 0.0.0.100 filter-list route-map rmap_1 in +# - area 0.0.0.100 filter-list route-map rmap_2 out +# - area 0.0.0.100 range 2001:db2::/32 not-advertise +# - area 0.0.0.100 range 2001:db3::/32 cost 120 +# - vrf zone1 +# - router-id 198.51.100.129 +# - area 0.0.0.102 nssa no-summary default-information-originate +# - area 0.0.0.103 nssa no-summary +# - area 0.0.0.103 nssa translate type7 always +# - vrf zone2 +# - auto-cost reference-bandwidth 45 Gbps +# - router ospf 100 +# - router-id 203.0.113.20 +# +# after: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - address_family: +# afi: ipv4 +# safi: unicast +# areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 2001:db2::/32 +# - cost: 120 +# prefix: 2001:db3::/32 +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# process_id: "102" +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# router ospfv3 100 +# router-id 203.0.113.20 +# router ospfv3 102 +# router-id 198.51.100.1 +# address-family ipv6 unicast +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 2001:db2::/32 not-advertise +# area 0.0.0.100 range 2001:db3::/32 cost 120 +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +# Using replaced + +# Before state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# router ospfv3 100 +# router-id 203.0.113.20 +# router ospfv3 102 +# router-id 198.51.100.1 +# address-family upv6 unicast +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 2001:db2::/32 not-advertise +# area 0.0.0.100 range 2001:db3::/32 cost 120 +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +- name: Replace device configurations of listed OSPFv3 processes with provided configurations + cisco.nxos.nxos_ospfv3: + config: + processes: + - process_id: 102 + router_id: 198.51.100.1 + address_family: + afi: ipv6 + safi: unicast + areas: + - area_id: 0.0.0.100 + filter_list: + - route_map: rmap_8 + direction: in + ranges: + - not_advertise: true + prefix: 2001:db2::/32 + redistribute: + - protocol: eigrp + id: 130 + route_map: rmap_1 + - protocol: direct + route_map: ospf102-direct-connect + vrfs: + - vrf: zone1 + router_id: 198.51.100.129 + areas: + - area_id: 0.0.0.102 + nssa: + default_information_originate: True + no_summary: True + state: replaced + +# Task output +# ------------- +# before: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - address_family: +# afi: ipv4 +# safi: unicast +# areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 2001:db2::/32 +# - cost: 120 +# prefix: 2001:db3::/32 +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# process_id: "102" +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +# commands: +# - router ospf 102 +# - address-family ipv6 unicast +# - redistribute eigrp 130 route-map rmap_1 +# - no redistribute eigrp 120 route-map rmap_1 +# - area 0.0.0.100 filter-list route-map rmap_8 in +# - no area 0.0.0.100 filter-list route-map rmap_2 out +# - no area 0.0.0.100 range 2001:db3::/32 +# - vrf zone1 +# - no area 0.0.0.103 nssa +# - no area 0.0.0.103 nssa translate type7 always +# - no vrf zone2 +# +# after: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - address_family: +# afi: ipv6 +# safi: unicast +# areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: in +# route_map: rmap_8 +# ranges: +# - not_advertise: true +# prefix: 2001:db2::/32 +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "130" +# protocol: eigrp +# route_map: rmap_1 +# process_id: "102" +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# router_id: 198.51.100.129 +# vrf: zone1 + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# router ospfv3 100 +# router-id 203.0.113.20 +# router ospfv3 102 +# router-id 198.51.100.1 +# address-family ipv6 unicast +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 130 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_8 in +# area 0.0.0.100 range 198.51.100.64/27 not-advertise +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate + +# Using overridden + +# Before state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# router ospfv3 100 +# router-id 203.0.113.20 +# router ospfv3 102 +# router-id 198.51.100.1 +# address-family ipv6 unicast +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 2001:db2::/32 not-advertise +# area 0.0.0.100 range 2001:db3::/32 cost 120 +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +- name: Override all OSPFv3 configuration with provided configuration + cisco.nxos.nxos_ospfv3: + config: + processes: + - process_id: 104 + router_id: 203.0.113.20 + - process_id: 102 + router_id: 198.51.100.1 + shutdown: true + state: overridden + +# Task output +# ------------- +# before: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - address_family: +# afi: ipv4 +# safi: unicast +# areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 2001:db2::/32 +# - cost: 120 +# prefix: 2001:db3::/32 +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# process_id: "102" +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +# commands: +# - no router ospfv3 100 +# - router ospfv3 104 +# - router-id 203.0.113.20 +# - router ospfv3 102 +# - shutdown +# - address-family ipv6 unicast +# - no redistribute direct route-map ospf102-direct-connect +# - no redistribute eigrp 120 route-map rmap_1 +# - no area 0.0.0.100 filter-list route-map rmap_2 out +# - no area 0.0.0.100 filter-list route-map rmap_1 in +# - no area 0.0.0.100 range 2001:db2::/32 +# - no area 0.0.0.100 range 2001:db3::/32 +# - no vrf zone1 +# - no vrf zone2 +# +# after: +# processes: +# - process_id: "102" +# router_id: 198.51.100.1 +# shutdown: true +# address_family: +# afi: ipv6 +# safi: unicast +# - process_id: "104" +# router_id: 203.0.113.20 + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# router ospfv3 102 +# router-id 198.51.100.1 +# address-family ipv6 unicast +# shutdown +# router ospfv3 104 +# router-id 203.0.113.20 + +# Using deleted to delete a single OSPF process + +# Before state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospf .*" +# router ospfv3 100 +# router-id 203.0.113.20 +# router ospfv3 102 +# router-id 198.51.100.1 +# address-family ipv6 unicast +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 2001:db2::/32 not-advertise +# area 0.0.0.100 range 2001:db3::/32 cost 120 +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +- name: Delete a single OSPFv3 process + cisco.nxos.nxos_ospfv3: + config: + processes: + - process_id: 102 + state: deleted + +# Task output +# ------------- +# before: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - address_family: +# afi: ipv4 +# safi: unicast +# areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 2001:db2::/32 +# - cost: 120 +# prefix: 2001:db3::/32 +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# process_id: "102" +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +# commands: +# - no router ospfv3 102 +# +# after: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# router ospfv3 100 +# router-id 203.0.113.20 + +# Using deleted all OSPFv3 processes from the device + +# Before state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# router ospfv3 100 +# router-id 203.0.113.20 +# router ospfv3 102 +# router-id 198.51.100.1 +# address-family ipv6 unicast +# redistribute direct route-map ospf102-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 2001:db2::/32 not-advertise +# area 0.0.0.100 range 2001:db3::/32 cost 120 +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.0.102 nssa no-summary default-information-originate +# area 0.0.0.103 nssa no-summary +# area 0.0.0.103 nssa translate type7 always +# vrf zone2 +# auto-cost reference-bandwidth 45 Gbps + +- name: Delete all OSPFv3 processes from the device + cisco.nxos.nxos_ospfv3: + state: deleted + +# Task output +# ------------- +# before: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - address_family: +# afi: ipv4 +# safi: unicast +# areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 2001:db2::/32 +# - cost: 120 +# prefix: 2001:db3::/32 +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# process_id: "102" +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +# commands: +# - no router ospfv3 100 +# - no router ospfv3 102 +# +# after: {} + +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^router ospfv3" +# nxos-9k-rdo# + +# Using rendered + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_ospfv3: + config: + processes: + - process_id: 100 + router_id: 203.0.113.20 + - process_id: 102 + router_id: 198.51.100.1 + address_family: + afi: ipv6 + safi: unicast + areas: + - area_id: 0.0.0.100 + filter_list: + - route_map: rmap_1 + direction: in + - route_map: rmap_2 + direction: out + ranges: + - prefix: 2001:db2::/32 + not_advertise: true + - prefix: 2001:db3::/32 + cost: 120 + redistribute: + - protocol: eigrp + id: 120 + route_map: rmap_1 + - protocol: direct + route_map: ospf102-direct-connect + vrfs: + - vrf: zone1 + router_id: 198.51.100.129 + areas: + - area_id: 0.0.0.102 + nssa: + default_information_originate: true + no_summary: true + - area_id: 0.0.0.103 + nssa: + no_summary: true + translate: + type7: + always: true + - vrf: zone2 + auto_cost: + reference_bandwidth: 45 + unit: Gbps + state: rendered + +# Task Output (redacted) +# ----------------------- +# rendered: +# - router ospfv3 100 +# - router-id 203.0.113.20 +# - router ospfv3 102 +# - router-id 198.51.100.1 +# - address-family ipv6 unicast +# - redistribute eigrp 120 route-map rmap_1 +# - redistribute direct route-map ospf102-direct-connect +# - area 0.0.0.100 filter-list route-map rmap_1 in +# - area 0.0.0.100 filter-list route-map rmap_2 out +# - area 0.0.0.100 range 2001:db2::/32 not-advertise +# - area 0.0.0.100 range 2001:db3::/32 cost 120 +# - vrf zone1 +# - router-id 198.51.100.129 +# - area 0.0.0.102 nssa no-summary default-information-originate +# - area 0.0.0.103 nssa no-summary +# - area 0.0.0.103 nssa translate type7 always +# - vrf zone2 +# - auto-cost reference-bandwidth 45 Gbps + +# Using parsed + +# parsed.cfg +# ------------ +# router ospfv3 100 +# router-id 192.0.100.1 +# address-family ipv6 unicast +# redistribute direct route-map ospf-direct-connect +# redistribute eigrp 120 route-map rmap_1 +# area 0.0.0.100 filter-list route-map rmap_2 out +# area 0.0.0.100 filter-list route-map rmap_1 in +# area 0.0.0.100 range 2001:db2::/32 not-advertise +# area 0.0.0.100 range 2001:db3::/32 cost 120 +# vrf zone1 +# router-id 198.51.100.129 +# area 0.0.100.1 nssa no-summary no-redistribution +# router ospfv3 102 +# router-id 198.54.100.1 +# shutdown + +- name: Parse externally provided OSPFv3 config + cisco.nxos.nxos_ospfv3: + running_config: "{{ lookup('file', 'ospfv2.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# processes: +# - process_id: "100" +# address_family: +# afi: ipv6 +# safi: unicast +# areas: +# - area_id: 0.0.0.101 +# nssa: +# no_redistribution: true +# no_summary: true +# - area_id: 0.0.0.102 +# stub: +# no_summary: true +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 192.0.2.0/24 +# - cost: 120 +# prefix: 192.0.3.0/24 +# redistribute: +# - protocol: direct +# route_map: ospf-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# router_id: 192.0.100.1 +# vrfs: +# - vrf: zone1 +# areas: +# - area_id: 0.0.100.1 +# nssa: +# no_redistribution: true +# no_summary: true +# router_id: 192.0.100.2 +# - process_id: "102" +# router_id: 198.54.100.1 +# shutdown: True + +# Using gathered + +- name: Gather OSPFv3 facts using gathered + cisco.nxos.nxos_ospfv3: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# processes: +# - process_id: "100" +# router_id: 203.0.113.20 +# - address_family: +# afi: ipv4 +# safi: unicast +# areas: +# - area_id: 0.0.0.100 +# filter_list: +# - direction: out +# route_map: rmap_2 +# - direction: in +# route_map: rmap_1 +# ranges: +# - not_advertise: true +# prefix: 2001:db2::/32 +# - cost: 120 +# prefix: 2001:db3::/32 +# redistribute: +# - protocol: direct +# route_map: ospf102-direct-connect +# - id: "120" +# protocol: eigrp +# route_map: rmap_1 +# process_id: "102" +# router_id: 198.51.100.1 +# vrfs: +# - areas: +# - area_id: 0.0.0.102 +# nssa: +# default_information_originate: true +# no_summary: true +# - area_id: 0.0.0.103 +# nssa: +# no_summary: true +# translate: +# type7: +# always: true +# router_id: 198.51.100.129 +# vrf: zone1 +# - auto_cost: +# reference_bandwidth: 45 +# unit: Gbps +# vrf: zone2 +# +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: + - "router ospfv3 102" + - "router-id 198.54.100.1" + - "router ospfv3 100" + - "router-id 192.0.100.1" + - "address-family ipv6 unicast" + - "redistribute eigrp 120 route-map rmap_1" + - "redistribute direct route-map ospf-direct-connect" + - "area 0.0.0.100 filter-list route-map rmap_1 in" + - "area 0.0.0.100 filter-list route-map rmap_2 out" + - "area 0.0.0.100 range 2001:db2::/32 not-advertise" + - "area 0.0.0.100 range 2001:db3::/32 cost 120" + - "vrf zone1" + - "router-id 192.0.100.2" + - "vrf zone2" + - "auto-cost reference-bandwidth 45 Gbps" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv3.ospfv3 import ( + Ospfv3Args, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospfv3.ospfv3 import ( + Ospfv3, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Ospfv3Args.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Ospfv3(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py new file mode 100644 index 00000000..70b25246 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_overlay_global +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Configures anycast gateway MAC of the switch. +description: +- Configures anycast gateway MAC of the switch. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- Default restores params default value +- Supported MAC address format are "E.E.E", "EE-EE-EE-EE-EE-EE", "EE:EE:EE:EE:EE:EE" + and "EEEE.EEEE.EEEE" +options: + anycast_gateway_mac: + description: + - Anycast gateway mac of the switch. + required: true + type: str +""" + +EXAMPLES = """ +- cisco.nxos.nxos_overlay_global: + anycast_gateway_mac: b.b.b +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["fabric forwarding anycast-gateway-mac 000B.000B.000B"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +PARAM_TO_COMMAND_KEYMAP = {"anycast_gateway_mac": "fabric forwarding anycast-gateway-mac"} + + +def get_existing(module, args): + existing = {} + config = str(get_config(module)) + + for arg in args: + command = PARAM_TO_COMMAND_KEYMAP[arg] + has_command = re.findall(r"(?:{0}\s)(?P<value>.*)$".format(command), config, re.M) + value = "" + if has_command: + value = has_command[0] + existing[arg] = value + + return existing + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if value: + new_dict[new_key] = value + return new_dict + + +def get_commands(module, existing, proposed, candidate): + commands = list() + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) + existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) + + for key, proposed in proposed_commands.items(): + existing_value = existing_commands.get(key) + if proposed == "default" and existing_value: + commands.append("no {0} {1}".format(key, existing_value)) + elif "anycast-gateway-mac" in key and proposed != "default": + proposed = normalize_mac(proposed, module) + existing_value = normalize_mac(existing_value, module) + if proposed != existing_value: + command = "{0} {1}".format(key, proposed) + commands.append(command) + if commands: + candidate.add(commands, parents=[]) + + +def normalize_mac(proposed_mac, module): + if proposed_mac is None: + return "" + try: + if "-" in proposed_mac: + splitted_mac = proposed_mac.split("-") + if len(splitted_mac) != 6: + raise ValueError + + for octect in splitted_mac: + if len(octect) != 2: + raise ValueError + + elif "." in proposed_mac: + splitted_mac = [] + splitted_dot_mac = proposed_mac.split(".") + if len(splitted_dot_mac) != 3: + raise ValueError + + for octect in splitted_dot_mac: + if len(octect) > 4: + raise ValueError + else: + octect_len = len(octect) + padding = 4 - octect_len + splitted_mac.append(octect.zfill(padding + 1)) + + elif ":" in proposed_mac: + splitted_mac = proposed_mac.split(":") + if len(splitted_mac) != 6: + raise ValueError + + for octect in splitted_mac: + if len(octect) != 2: + raise ValueError + else: + raise ValueError + except ValueError: + module.fail_json(msg="Invalid MAC address format", proposed_mac=proposed_mac) + + joined_mac = "".join(splitted_mac) + # fmt: off + mac = [joined_mac[i: i + 4] for i in range(0, len(joined_mac), 4)] + # fmt: on + return ".".join(mac).upper() + + +def main(): + argument_spec = dict(anycast_gateway_mac=dict(required=True, type="str")) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + result = {"changed": False, "commands": [], "warnings": warnings} + + args = PARAM_TO_COMMAND_KEYMAP.keys() + + existing = get_existing(module, args) + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + + candidate = CustomNetworkConfig(indent=3) + get_commands(module, existing, proposed, candidate) + + if candidate: + candidate = candidate.items_text() + result["commands"] = candidate + + if not module.check_mode: + load_config(module, candidate) + result["changed"] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py new file mode 100644 index 00000000..d9fc6f46 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py @@ -0,0 +1,216 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_pim +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages configuration of a PIM instance. +description: +- Manages configuration of a Protocol Independent Multicast (PIM) instance. +notes: +- Unsupported for Cisco MDS +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +options: + bfd: + description: + - Enables BFD on all PIM interfaces. + - "Dependency: ''feature bfd''" + type: str + choices: + - enable + - disable + ssm_range: + description: + - Configure group ranges for Source Specific Multicast (SSM). Valid values are + multicast addresses or the keyword C(none) or keyword C(default). C(none) removes + all SSM group ranges. C(default) will set ssm_range to the default multicast + address. If you set multicast address, please ensure that it is not the same + as the C(default), otherwise use the C(default) option. + type: list + default: [] + elements: str +""" +EXAMPLES = """ +- name: Configure ssm_range, enable bfd + cisco.nxos.nxos_pim: + bfd: enable + ssm_range: 224.0.0.0/8 + +- name: Set to default + cisco.nxos.nxos_pim: + ssm_range: default + +- name: Remove all ssm group ranges + cisco.nxos.nxos_pim: + ssm_range: none +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: + - ip pim bfd + - ip pim ssm range 224.0.0.0/8 +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +PARAM_TO_COMMAND_KEYMAP = { + "bfd": "ip pim bfd", + "ssm_range": "ip pim ssm range", +} + + +def get_existing(module, args): + existing = {} + config = str(get_config(module)) + + for arg in args: + if "ssm_range" in arg: + # <value> may be 'n.n.n.n/s', 'none', or 'default' + m = re.search( + r"ssm range (?P<value>(?:[\s\d.\/]+|none|default))?$", + config, + re.M, + ) + if m: + # Remove rsvd SSM range + value = m.group("value").replace("232.0.0.0/8", "") + existing[arg] = value.split() + + elif "bfd" in arg and "ip pim bfd" in config: + existing[arg] = "enable" + + return existing + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if value is not None: + new_dict[new_key] = value + return new_dict + + +def get_commands(module, existing, proposed, candidate): + commands = list() + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) + + for key, value in proposed_commands.items(): + command = "" + if key == "ip pim ssm range": + if value == "default": + # no cmd needs a value but the actual value does not matter + command = "no ip pim ssm range none" + elif value == "none": + command = "ip pim ssm range none" + elif value: + command = "ip pim ssm range {0}".format(value) + elif key == "ip pim bfd": + no_cmd = "no " if value == "disable" else "" + command = no_cmd + key + + if command: + commands.append(command) + + if commands: + candidate.add(commands, parents=[]) + + +def main(): + argument_spec = dict( + bfd=dict(required=False, type="str", choices=["enable", "disable"]), + ssm_range=dict(required=False, type="list", default=[], elements="str"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + warnings = list() + result = {"changed": False, "commands": [], "warnings": warnings} + + params = module.params + args = [k for k in PARAM_TO_COMMAND_KEYMAP.keys() if params[k] is not None] + + # SSM syntax check + if "ssm_range" in args: + for item in params["ssm_range"]: + if re.search("none|default", item): + break + if len(item.split(".")) != 4: + module.fail_json( + msg="Valid ssm_range values are multicast addresses " + "or the keyword 'none' or the keyword 'default'.", + ) + + existing = get_existing(module, args) + proposed_args = dict((k, v) for k, v in params.items() if k in args) + + proposed = {} + for key, value in proposed_args.items(): + if key == "ssm_range": + if value and value[0] == "default": + if existing.get(key): + proposed[key] = "default" + else: + v = sorted(set([str(i) for i in value])) + ex = sorted(set([str(i) for i in existing.get(key, [])])) + if v != ex: + proposed[key] = " ".join(str(s) for s in v) + + elif key == "bfd": + if value != existing.get("bfd", "disable"): + proposed[key] = value + + elif value != existing.get(key): + proposed[key] = value + + candidate = CustomNetworkConfig(indent=3) + get_commands(module, existing, proposed, candidate) + + if candidate: + candidate = candidate.items_text() + result["commands"] = candidate + result["changed"] = True + load_config(module, candidate) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py new file mode 100644 index 00000000..4d0f1052 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py @@ -0,0 +1,604 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = """ +module: nxos_pim_interface +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages PIM interface configuration. +description: +- Manages PIM interface configuration settings. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- When C(state=default), supported params will be reset to a default state. These + include C(dr_prio), C(hello_auth_key), C(hello_interval), C(jp_policy_out), C(jp_policy_in), + C(jp_type_in), C(jp_type_out), C(border), C(neighbor_policy), C(neighbor_type). +- The C(hello_auth_key) param is not idempotent. +- C(hello_auth_key) only supports clear text passwords. +- When C(state=absent), pim interface configuration will be set to defaults and pim-sm + will be disabled on the interface. +- PIM must be enabled on the device to use this module. +- This module is for Layer 3 interfaces. +options: + interface: + description: + - Full name of the interface such as Ethernet1/33. + type: str + required: true + sparse: + description: + - Enable/disable sparse-mode on the interface. + type: bool + default: false + bfd: + description: + - Enables BFD for PIM at the interface level. This overrides the bfd variable + set at the pim global level. + - Valid values are 'enable', 'disable' or 'default'. + - "Dependency: ''feature bfd''" + type: str + choices: + - enable + - disable + - default + dr_prio: + description: + - Configures priority for PIM DR election on interface. + type: str + hello_auth_key: + description: + - Authentication for hellos on this interface. + type: str + hello_interval: + description: + - Hello interval in milliseconds or seconds for this interface. + - Use the option I(hello_interval_ms) to specify if the given value is in + milliseconds or seconds. The default is seconds. + type: int + hello_interval_ms: + description: + - Specifies that the hello_interval is in milliseconds. + - When set to True, this indicates that the user is providing the + hello_interval in milliseconds and hence, no conversion is required. + type: bool + version_added: 2.0.0 + jp_policy_out: + description: + - Policy for join-prune messages (outbound). + type: str + jp_policy_in: + description: + - Policy for join-prune messages (inbound). + type: str + jp_type_out: + description: + - Type of policy mapped to C(jp_policy_out). + type: str + choices: + - prefix + - routemap + jp_type_in: + description: + - Type of policy mapped to C(jp_policy_in). + type: str + choices: + - prefix + - routemap + border: + description: + - Configures interface to be a boundary of a PIM domain. + type: bool + default: false + neighbor_policy: + description: + - Configures a neighbor policy for filtering adjacencies. + type: str + neighbor_type: + description: + - Type of policy mapped to neighbor_policy. + type: str + choices: + - prefix + - routemap + state: + description: + - Manages desired state of the resource. + type: str + choices: + - present + - absent + - default + default: present +""" +EXAMPLES = """ +- name: Ensure PIM is not running on the interface + cisco.nxos.nxos_pim_interface: + interface: eth1/33 + state: absent + +- name: Ensure the interface has pim-sm enabled with the appropriate priority and + hello interval + cisco.nxos.nxos_pim_interface: + interface: eth1/33 + dr_prio: 10 + hello_interval: 40 + state: present + +- name: Ensure join-prune policies exist + cisco.nxos.nxos_pim_interface: + interface: eth1/33 + jp_policy_in: JPIN + jp_policy_out: JPOUT + jp_type_in: routemap + jp_type_out: routemap + +- name: disable bfd on the interface + cisco.nxos.nxos_pim_interface: + interface: eth1/33 + bfd: disable + +- name: Ensure defaults are in place + cisco.nxos.nxos_pim_interface: + interface: eth1/33 + state: default +""" + +RETURN = r""" +commands: + description: command sent to the device + returned: always + type: list + sample: ["interface eth1/33", + "ip pim neighbor-policy test", + "ip pim bfd-instance disable", + "ip pim neighbor-policy test" + ] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + get_interface_type, + load_config, + run_commands, +) + + +PARAM_TO_COMMAND_KEYMAP = { + "interface": "", + "bfd": "ip pim bfd-instance", + "sparse": "ip pim sparse-mode", + "dr_prio": "ip pim dr-priority {0}", + "hello_interval": "ip pim hello-interval {0}", + "hello_auth_key": "ip pim hello-authentication ah-md5 {0}", + "border": "ip pim border", + "jp_policy_out": "ip pim jp-policy prefix-list {0} out", + "jp_policy_in": "ip pim jp-policy prefix-list {0} in", + "jp_type_in": "", + "jp_type_out": "", + "neighbor_policy": "ip pim neighbor-policy prefix-list {0}", + "neighbor_type": "", +} + +PARAM_TO_DEFAULT_KEYMAP = { + "bfd": "default", + "dr_prio": "1", + "hello_interval": "30000", + "sparse": False, + "border": False, + "hello_auth_key": False, +} + +BFD_KEYMAP = { + None: None, + "default": "no ip pim bfd-instance", + "disable": "ip pim bfd-instance disable", + "enable": "ip pim bfd-instance", +} + + +def execute_show_command(command, module, text=False): + if text: + cmds = [{"command": command, "output": "text"}] + else: + cmds = [{"command": command, "output": "json"}] + + return run_commands(module, cmds) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def local_existing(gexisting): + jp_bidir = False + isauth = False + if gexisting: + jp_bidir = gexisting.get("jp_bidir") + isauth = gexisting.get("isauth") + if jp_bidir and isauth: + gexisting.pop("jp_bidir") + gexisting.pop("isauth") + + return gexisting, jp_bidir, isauth + + +def get_interface_mode(interface, intf_type, module): + mode = "unknown" + command = "show interface {0}".format(interface) + body = execute_show_command(command, module) + + try: + interface_table = body[0]["TABLE_interface"]["ROW_interface"] + except (KeyError, AttributeError, IndexError): + return mode + + if intf_type in ["ethernet", "portchannel"]: + mode = str(interface_table.get("eth_mode", "layer3")) + if mode in ["access", "trunk"]: + mode = "layer2" + elif mode == "routed": + mode = "layer3" + elif intf_type in ["loopback", "svi"]: + mode = "layer3" + return mode + + +def get_pim_interface(module, interface): + pim_interface = {} + body = get_config(module, flags=["interface {0}".format(interface)]) + + pim_interface["bfd"] = "default" + pim_interface["neighbor_type"] = None + pim_interface["neighbor_policy"] = None + pim_interface["jp_policy_in"] = None + pim_interface["jp_policy_out"] = None + pim_interface["jp_type_in"] = None + pim_interface["jp_type_out"] = None + pim_interface["jp_bidir"] = False + pim_interface["isauth"] = False + + if body: + all_lines = body.splitlines() + + for each in all_lines: + if "jp-policy" in each: + policy_name = re.search( + r"ip pim jp-policy(?: prefix-list)? (\S+)(?: \S+)?", + each, + ).group(1) + if "prefix-list" in each: + ptype = "prefix" + else: + ptype = "routemap" + if "out" in each: + pim_interface["jp_policy_out"] = policy_name + pim_interface["jp_type_out"] = ptype + elif "in" in each: + pim_interface["jp_policy_in"] = policy_name + pim_interface["jp_type_in"] = ptype + else: + pim_interface["jp_policy_in"] = policy_name + pim_interface["jp_policy_out"] = policy_name + pim_interface["jp_bidir"] = True + elif "neighbor-policy" in each: + pim_interface["neighbor_policy"] = re.search( + r"ip pim neighbor-policy(?: prefix-list)? (\S+)", + each, + ).group(1) + if "prefix-list" in each: + pim_interface["neighbor_type"] = "prefix" + else: + pim_interface["neighbor_type"] = "routemap" + elif "ah-md5" in each: + pim_interface["isauth"] = True + elif "sparse-mode" in each: + pim_interface["sparse"] = True + elif "bfd-instance" in each: + m = re.search(r"ip pim bfd-instance(?P<disable> disable)?", each) + if m: + pim_interface["bfd"] = "disable" if m.group("disable") else "enable" + elif "border" in each: + pim_interface["border"] = True + elif "hello-interval" in each: + pim_interface["hello_interval"] = re.search( + r"ip pim hello-interval (\d+)", + body, + ).group(1) + elif "dr-priority" in each: + pim_interface["dr_prio"] = re.search(r"ip pim dr-priority (\d+)", body).group(1) + + return pim_interface + + +def fix_delta(delta, existing): + for key in list(delta): + if key in ["dr_prio", "hello_interval", "sparse", "border"]: + if delta.get(key) == PARAM_TO_DEFAULT_KEYMAP.get(key) and existing.get(key) is None: + delta.pop(key) + return delta + + +def config_pim_interface(delta, existing, jp_bidir, isauth): + command = None + commands = [] + + delta = fix_delta(delta, existing) + + if jp_bidir: + if delta.get("jp_policy_in") or delta.get("jp_policy_out"): + if existing.get("jp_type_in") == "prefix": + command = "no ip pim jp-policy prefix-list {0}".format(existing.get("jp_policy_in")) + else: + command = "no ip pim jp-policy {0}".format(existing.get("jp_policy_in")) + if command: + commands.append(command) + + for k, v in delta.items(): + if k in [ + "bfd", + "dr_prio", + "hello_interval", + "hello_auth_key", + "border", + "sparse", + ]: + if k == "bfd": + command = BFD_KEYMAP[v] + elif v: + command = PARAM_TO_COMMAND_KEYMAP.get(k).format(v) + elif k == "hello_auth_key": + if isauth: + command = "no ip pim hello-authentication ah-md5" + else: + command = "no " + PARAM_TO_COMMAND_KEYMAP.get(k).format(v) + + if command: + commands.append(command) + elif k in [ + "neighbor_policy", + "jp_policy_in", + "jp_policy_out", + "neighbor_type", + ]: + if k in ["neighbor_policy", "neighbor_type"]: + temp = delta.get("neighbor_policy") or existing.get("neighbor_policy") + if delta.get("neighbor_type") == "prefix": + command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp) + elif delta.get("neighbor_type") == "routemap": + command = "ip pim neighbor-policy {0}".format(temp) + elif existing.get("neighbor_type") == "prefix": + command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp) + elif existing.get("neighbor_type") == "routemap": + command = "ip pim neighbor-policy {0}".format(temp) + elif k in ["jp_policy_in", "jp_type_in"]: + temp = delta.get("jp_policy_in") or existing.get("jp_policy_in") + if delta.get("jp_type_in") == "prefix": + command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp) + elif delta.get("jp_type_in") == "routemap": + command = "ip pim jp-policy {0} in".format(temp) + elif existing.get("jp_type_in") == "prefix": + command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp) + elif existing.get("jp_type_in") == "routemap": + command = "ip pim jp-policy {0} in".format(temp) + elif k in ["jp_policy_out", "jp_type_out"]: + temp = delta.get("jp_policy_out") or existing.get("jp_policy_out") + if delta.get("jp_type_out") == "prefix": + command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp) + elif delta.get("jp_type_out") == "routemap": + command = "ip pim jp-policy {0} out".format(temp) + elif existing.get("jp_type_out") == "prefix": + command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp) + elif existing.get("jp_type_out") == "routemap": + command = "ip pim jp-policy {0} out".format(temp) + if command: + commands.append(command) + command = None + + if "no ip pim sparse-mode" in commands: + # sparse is long-running on some platforms, process it last + commands.remove("no ip pim sparse-mode") + commands.append("no ip pim sparse-mode") + return commands + + +def get_pim_interface_defaults(): + args = dict( + dr_prio=PARAM_TO_DEFAULT_KEYMAP.get("dr_prio"), + bfd=PARAM_TO_DEFAULT_KEYMAP.get("bfd"), + border=PARAM_TO_DEFAULT_KEYMAP.get("border"), + sparse=PARAM_TO_DEFAULT_KEYMAP.get("sparse"), + hello_interval=PARAM_TO_DEFAULT_KEYMAP.get("hello_interval"), + hello_auth_key=PARAM_TO_DEFAULT_KEYMAP.get("hello_auth_key"), + ) + + default = dict((param, value) for (param, value) in args.items() if value is not None) + + return default + + +def default_pim_interface_policies(existing, jp_bidir): + commands = [] + + if jp_bidir: + if existing.get("jp_policy_in") or existing.get("jp_policy_out"): + if existing.get("jp_type_in") == "prefix": + command = "no ip pim jp-policy prefix-list {0}".format(existing.get("jp_policy_in")) + if command: + commands.append(command) + + elif not jp_bidir: + command = None + for k in existing: + if k == "jp_policy_in": + if existing.get("jp_policy_in"): + if existing.get("jp_type_in") == "prefix": + command = "no ip pim jp-policy prefix-list {0} in".format( + existing.get("jp_policy_in"), + ) + else: + command = "no ip pim jp-policy {0} in".format(existing.get("jp_policy_in")) + elif k == "jp_policy_out": + if existing.get("jp_policy_out"): + if existing.get("jp_type_out") == "prefix": + command = "no ip pim jp-policy prefix-list {0} out".format( + existing.get("jp_policy_out"), + ) + else: + command = "no ip pim jp-policy {0} out".format( + existing.get("jp_policy_out"), + ) + if command: + commands.append(command) + command = None + + if existing.get("neighbor_policy"): + command = "no ip pim neighbor-policy" + commands.append(command) + + return commands + + +def config_pim_interface_defaults(existing, jp_bidir, isauth): + command = [] + + # returns a dict + defaults = get_pim_interface_defaults() + delta = dict(set(defaults.items()).difference(existing.items())) + if delta: + # returns a list + command = config_pim_interface(delta, existing, jp_bidir, isauth) + comm = default_pim_interface_policies(existing, jp_bidir) + if comm: + for each in comm: + command.append(each) + + return command + + +def normalize_proposed_values(proposed, module): + keys = proposed.keys() + if "bfd" in keys: + # bfd is a tri-state string: enable, disable, default + proposed["bfd"] = proposed["bfd"].lower() + if "hello_interval" in keys: + hello_interval = proposed["hello_interval"] + if not module.params["hello_interval_ms"]: + hello_interval = hello_interval * 1000 + proposed["hello_interval"] = str(hello_interval) + + +def main(): + argument_spec = dict( + interface=dict(type="str", required=True), + sparse=dict(type="bool", default=False), + dr_prio=dict(type="str"), + hello_auth_key=dict(type="str", no_log=True), + hello_interval=dict(type="int"), + hello_interval_ms=dict(type="bool"), + jp_policy_out=dict(type="str"), + jp_policy_in=dict(type="str"), + jp_type_out=dict(type="str", choices=["prefix", "routemap"]), + jp_type_in=dict(type="str", choices=["prefix", "routemap"]), + bfd=dict(type="str", choices=["enable", "disable", "default"]), + border=dict(type="bool", default=False), + neighbor_policy=dict(type="str"), + neighbor_type=dict(type="str", choices=["prefix", "routemap"]), + state=dict( + type="str", + default="present", + choices=["absent", "default", "present"], + ), + ) + + required_by = {"hello_interval_ms": "hello_interval"} + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_by=required_by, + ) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + state = module.params["state"] + interface = module.params["interface"] + jp_type_in = module.params["jp_type_in"] + jp_type_out = module.params["jp_type_out"] + jp_policy_in = module.params["jp_policy_in"] + jp_policy_out = module.params["jp_policy_out"] + neighbor_policy = module.params["neighbor_policy"] + neighbor_type = module.params["neighbor_type"] + + intf_type = get_interface_type(interface) + if get_interface_mode(interface, intf_type, module) == "layer2": + module.fail_json(msg="this module only works on Layer 3 interfaces.") + + if jp_policy_in: + if not jp_type_in: + module.fail_json(msg="jp_type_in required when using jp_policy_in.") + if jp_policy_out: + if not jp_type_out: + module.fail_json(msg="jp_type_out required when using jp_policy_out.") + if neighbor_policy: + if not neighbor_type: + module.fail_json(msg="neighbor_type required when using neighbor_policy.") + + get_existing = get_pim_interface(module, interface) + existing, jp_bidir, isauth = local_existing(get_existing) + + args = PARAM_TO_COMMAND_KEYMAP.keys() + proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + normalize_proposed_values(proposed, module) + + delta = dict(set(proposed.items()).difference(existing.items())) + + commands = [] + if state == "present": + if delta: + command = config_pim_interface(delta, existing, jp_bidir, isauth) + if command: + commands.append(command) + elif state == "default" or state == "absent": + defaults = config_pim_interface_defaults(existing, jp_bidir, isauth) + if defaults: + commands.append(defaults) + + if commands: + commands.insert(0, ["interface {0}".format(interface)]) + + cmds = flatten_list(commands) + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + if "configure" in cmds: + cmds.pop(0) + + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py new file mode 100644 index 00000000..fe9989ed --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py @@ -0,0 +1,248 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_pim_rp_address +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages configuration of an PIM static RP address instance. +description: +- Manages configuration of an Protocol Independent Multicast (PIM) static rendezvous + point (RP) address instance. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- C(state=absent) is currently not supported on all platforms. +options: + rp_address: + description: + - Configures a Protocol Independent Multicast (PIM) static rendezvous point (RP) + address. Valid values are unicast addresses. + required: true + type: str + group_list: + description: + - Group range for static RP. Valid values are multicast addresses. + type: str + prefix_list: + description: + - Prefix list policy for static RP. Valid values are prefix-list policy names. + type: str + route_map: + description: + - Route map policy for static RP. Valid values are route-map policy names. + type: str + bidir: + description: + - Group range is treated in PIM bidirectional mode. + type: bool + state: + description: + - Specify desired state of the resource. + default: present + choices: + - present + - absent + type: str +""" +EXAMPLES = """ +- cisco.nxos.nxos_pim_rp_address: + rp_address: 10.1.1.20 + state: present +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["router bgp 65535", "vrf test", "router-id 192.0.2.1"] +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +def get_existing(module, args, gl): + existing = {} + config = str(get_config(module)) + address = module.params["rp_address"] + + pim_address_re = r"ip pim rp-address (?P<value>.*)$" + for line in re.findall(pim_address_re, config, re.M): + values = line.split() + if values[0] != address: + continue + if gl and "group-list" not in line: + continue + elif not gl and "group-list" in line: + if "224.0.0.0/4" not in line: # ignore default group-list + continue + + existing["bidir"] = existing.get("bidir") or "bidir" in line + if len(values) > 2: + value = values[2] + if values[1] == "route-map": + existing["route_map"] = value + elif values[1] == "prefix-list": + existing["prefix_list"] = value + elif values[1] == "group-list": + if value != "224.0.0.0/4": # ignore default group-list + existing["group_list"] = value + + return existing + + +def state_present(module, existing, proposed, candidate): + address = module.params["rp_address"] + command = "ip pim rp-address {0}".format(address) + if module.params["group_list"] and not proposed.get("group_list"): + command += " group-list " + module.params["group_list"] + if module.params["prefix_list"]: + if not proposed.get("prefix_list"): + command += " prefix-list " + module.params["prefix_list"] + if module.params["route_map"]: + if not proposed.get("route_map"): + command += " route-map " + module.params["route_map"] + commands = build_command(proposed, command) + if commands: + candidate.add(commands, parents=[]) + + +def build_command(param_dict, command): + for param in ["group_list", "prefix_list", "route_map"]: + if param_dict.get(param): + command += " {0} {1}".format(param.replace("_", "-"), param_dict.get(param)) + if param_dict.get("bidir"): + command += " bidir" + return [command] + + +def state_absent(module, existing, candidate): + address = module.params["rp_address"] + + commands = [] + command = "no ip pim rp-address {0}".format(address) + if module.params["group_list"] == existing.get("group_list"): + commands = build_command(existing, command) + elif not module.params["group_list"]: + commands = [command] + + if commands: + candidate.add(commands, parents=[]) + + +def get_proposed(pargs, existing): + proposed = {} + + for key, value in pargs.items(): + if key != "rp_address": + if str(value).lower() == "true": + value = True + elif str(value).lower() == "false": + value = False + + if existing.get(key) != value: + proposed[key] = value + + return proposed + + +def main(): + argument_spec = dict( + rp_address=dict(required=True, type="str"), + group_list=dict(required=False, type="str"), + prefix_list=dict(required=False, type="str"), + route_map=dict(required=False, type="str"), + bidir=dict(required=False, type="bool"), + state=dict(choices=["present", "absent"], default="present", required=False), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ["group_list", "route_map"], + ["group_list", "prefix_list"], + ["route_map", "prefix_list"], + ], + supports_check_mode=True, + ) + + warnings = list() + result = {"changed": False, "commands": [], "warnings": warnings} + + state = module.params["state"] + + args = ["rp_address", "group_list", "prefix_list", "route_map", "bidir"] + + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + + if module.params["group_list"]: + existing = get_existing(module, args, True) + proposed = get_proposed(proposed_args, existing) + + else: + existing = get_existing(module, args, False) + proposed = get_proposed(proposed_args, existing) + + candidate = CustomNetworkConfig(indent=3) + if state == "present" and (proposed or not existing): + state_present(module, existing, proposed, candidate) + elif state == "absent" and existing: + state_absent(module, existing, candidate) + + if candidate: + candidate = candidate.items_text() + result["commands"] = candidate + result["changed"] = True + msgs = load_config(module, candidate, True) + if msgs: + for item in msgs: + if item: + if isinstance(item, dict): + err_str = item["clierror"] + else: + err_str = item + if "No policy was configured" in err_str: + if state == "absent": + addr = module.params["rp_address"] + new_cmd = "no ip pim rp-address {0}".format(addr) + load_config(module, new_cmd) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py new file mode 100644 index 00000000..31c8517a --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py @@ -0,0 +1,255 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_ping +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Tests reachability using ping from Nexus switch. +description: +- Tests reachability using ping from switch to a remote destination. +- For a general purpose network module, see the M(ansible.netcommon.net_ping) module. +- For Windows targets, use the M(ansible.windows.win_ping) module instead. +- For targets running Python, use the M(ansible.builtin.ping) module instead. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +options: + dest: + description: + - IP address or hostname (resolvable by switch) of remote node. + required: true + type: str + count: + description: + - Number of packets to send. + default: 5 + type: int + source: + description: + - Source IP Address or hostname (resolvable by switch) + type: str + vrf: + description: + - Outgoing VRF. + type: str + df_bit: + description: + - Set the DF bit. + default: false + type: bool + size: + description: + - Size of packets to send. + type: int + state: + description: + - Determines if the expected result is success or fail. + choices: + - absent + - present + default: present + type: str +notes: +- Unsupported for Cisco MDS +- For a general purpose network module, see the M(ansible.netcommon.net_ping) module. +- For Windows targets, use the M(ansible.windows.win_ping) module instead. +- For targets running Python, use the M(ansible.builtin.ping) module instead. +""" + +EXAMPLES = """ +- name: Test reachability to 8.8.8.8 using mgmt vrf + cisco.nxos.nxos_ping: + dest: 8.8.8.8 + vrf: management + host: 68.170.147.165 + +- name: Test reachability to a few different public IPs using mgmt vrf + cisco.nxos.nxos_ping: + dest: "{{ item }}" + vrf: management + host: 68.170.147.165 + with_items: + - 8.8.8.8 + - 4.4.4.4 + - 198.6.1.4 + +- name: Test reachability to 8.8.8.8 using mgmt vrf, size and df-bit + cisco.nxos.nxos_ping: + dest: 8.8.8.8 + df_bit: true + size: 1400 + vrf: management +""" + +RETURN = """ +commands: + description: Show the command sent + returned: always + type: list + sample: ["ping 8.8.8.8 count 2 vrf management"] +rtt: + description: Show RTT stats + returned: always + type: dict + sample: {"avg": 6.264, "max": 6.564, "min": 5.978} +packets_rx: + description: Packets successfully received + returned: always + type: int + sample: 2 +packets_tx: + description: Packets successfully transmitted + returned: always + type: int + sample: 2 +packet_loss: + description: Percentage of packets lost + returned: always + type: str + sample: "0.00%" +""" +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands + + +def get_summary(results_list, reference_point): + summary_string = results_list[reference_point + 1] + summary_list = summary_string.split(",") + + summary = dict( + packets_tx=int(summary_list[0].split("packets")[0].strip()), + packets_rx=int(summary_list[1].split("packets")[0].strip()), + packet_loss=summary_list[2].split("packet")[0].strip(), + ) + + if "bytes from" not in results_list[reference_point - 2]: + ping_pass = False + else: + ping_pass = True + + return summary, ping_pass + + +def get_rtt(results_list, packet_loss, location): + rtt = dict(min=None, avg=None, max=None) + + if packet_loss != "100.00%": + rtt_string = results_list[location] + base = rtt_string.split("=")[1] + rtt_list = base.split("/") + + rtt["min"] = float(rtt_list[0].lstrip()) + rtt["avg"] = float(rtt_list[1]) + rtt["max"] = float(rtt_list[2][:-3]) + + return rtt + + +def get_statistics_summary_line(response_as_list): + for each in response_as_list: + if "---" in each: + index = response_as_list.index(each) + return index + + +def get_ping_results(command, module): + cmd = {"command": command, "output": "text"} + ping = run_commands(module, [cmd])[0] + + if not ping: + module.fail_json( + msg="An unexpected error occurred. Check all params.", + command=command, + destination=module.params["dest"], + vrf=module.params["vrf"], + source=module.params["source"], + size=module.params["size"], + df_bit=module.params["df_bit"], + ) + + elif "can't bind to address" in ping: + module.fail_json(msg="Can't bind to source address.", command=command) + elif "bad context" in ping: + module.fail_json( + msg="Wrong VRF name inserted.", + command=command, + vrf=module.params["vrf"], + ) + else: + splitted_ping = ping.split("\n") + reference_point = get_statistics_summary_line(splitted_ping) + summary, ping_pass = get_summary(splitted_ping, reference_point) + rtt = get_rtt(splitted_ping, summary["packet_loss"], reference_point + 2) + + return (summary, rtt, ping_pass) + + +def main(): + argument_spec = dict( + dest=dict(required=True), + count=dict(required=False, default=5, type="int"), + vrf=dict(required=False), + source=dict(required=False), + size=dict(required=False, type="int"), + df_bit=dict(required=False, default=False, type="bool"), + state=dict(required=False, choices=["present", "absent"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + destination = module.params["dest"] + state = module.params["state"] + size = module.params["size"] + df_bit = module.params["df_bit"] + + ping_command = "ping {0}".format(destination) + for command in ["count", "source", "vrf"]: + arg = module.params[command] + if arg: + ping_command += " {0} {1}".format(command, arg) + + if size: + ping_command += " packet-size {0}".format(size) + + if df_bit: + ping_command += " df-bit" + + summary, rtt, ping_pass = get_ping_results(ping_command, module) + + results = summary + results["rtt"] = rtt + results["commands"] = [ping_command] + + if ping_pass and state == "absent": + module.fail_json(msg="Ping succeeded unexpectedly") + elif not ping_pass and state == "present": + module.fail_json(msg="Ping failed unexpectedly") + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py new file mode 100644 index 00000000..cbd2415c --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py @@ -0,0 +1,842 @@ +#!/usr/bin/python +# -*- 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 module file for nxos_prefix_lists +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_prefix_lists +short_description: Prefix-Lists resource module. +description: +- This module manages prefix-lists configuration on devices running Cisco NX-OS. +version_added: 2.4.0 +notes: +- Tested against NX-OS 9.3.6. +- Unsupported for Cisco MDS +- This module works with connection C(network_cli) and C(httpapi). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^ip(.*) prefix-list'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of prefix-list configuration. + type: list + elements: dict + suboptions: + afi: + description: + - The Address Family Identifier (AFI) for the prefix-lists. + type: str + choices: ["ipv4", "ipv6"] + prefix_lists: + description: List of prefix-list configurations. + type: list + elements: dict + suboptions: + name: + description: Name of the prefix-list. + type: str + description: + description: Description of the prefix list + type: str + entries: + description: List of configurations for the specified prefix-list + type: list + elements: dict + suboptions: + sequence: + description: Sequence Number. + type: int + action: + description: Prefix-List permit or deny. + type: str + choices: ["permit", "deny"] + prefix: + description: IP or IPv6 prefix in A.B.C.D/LEN or A:B::C:D/LEN format. + type: str + eq: + description: Exact prefix length to be matched. + type: int + ge: + description: Minimum prefix length to be matched. + type: int + le: + description: Maximum prefix length to be matched. + type: int + mask: + description: Explicit match mask. + type: str + state: + description: + - The state the configuration should be left in. + - Refer to examples for more details. + - With state I(replaced), for the listed prefix-lists, + sequences that are in running-config but not in the task are negated. + - With state I(overridden), all prefix-lists that are in running-config but + not in the task are negated. + - Please refer to examples for more details. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# nxos-9k-rdo# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_prefix_lists: + config: + - afi: ipv4 + prefix_lists: + - name: AllowPrefix + description: allows engineering IPv4 networks + entries: + - sequence: 10 + action: permit + prefix: 192.0.2.0/23 + eq: 24 + - sequence: 20 + action: permit + prefix: 198.51.100.128/26 + - name: DenyPrefix + description: denies lab IPv4 networks + entries: + - sequence: 20 + action: deny + prefix: 203.0.113.0/24 + le: 25 + + - afi: ipv6 + prefix_lists: + - name: AllowIPv6Prefix + description: allows engineering IPv6 networks + entries: + - sequence: 8 + action: permit + prefix: "2001:db8:400::/38" + - sequence: 20 + action: permit + prefix: "2001:db8:8000::/35" + le: 37 + +# Task output +# ------------- +# before: [] +# +# commands: +# - "ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks" +# - "ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38" +# - "ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37" +# - "ip prefix-list AllowPrefix description allows engineering IPv4 networks" +# - "ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24" +# - "ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26" +# - "ip prefix-list DenyPrefix description denies lab IPv4 networks" +# - "ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25" +# +# after: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 192.0.2.0/23 +# eq: 24 +# - sequence: 20 +# action: permit +# prefix: 198.51.100.128/26 +# name: AllowPrefix +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix + +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24 +# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26 +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +# Using replaced + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24 +# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26 +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +- name: Replace prefix-lists configurations of listed prefix-lists with provided configurations + cisco.nxos.nxos_prefix_lists: + config: + - afi: ipv4 + prefix_lists: + - name: AllowPrefix + description: allows engineering IPv4 networks + entries: + - sequence: 10 + action: permit + prefix: 203.0.113.64/27 + + - sequence: 30 + action: permit + prefix: 203.0.113.96/27 + - name: AllowPrefix2Stub + description: allow other engineering IPv4 network + state: replaced + +# Task output +# ------------- +# before: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 192.0.2.0/23 +# eq: 24 +# - sequence: 20 +# action: permit +# prefix: 198.51.100.128/26 +# name: AllowPrefix +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +# +# commands: +# - "no ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24" +# - "ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27" +# - "ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27" +# - "no ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26" +# - "ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network" +# +# after: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 203.0.113.64/27 +# - sequence: 30 +# action: permit +# prefix: 203.0.113.96/27 +# name: AllowPrefix +# - description: allow other engineering IPv4 network +# name: AllowPrefix2Stub +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +# +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27 +# ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27 +# ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +# Using overridden + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24 +# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26 +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +- name: Override all prefix-lists configuration with provided configuration + cisco.nxos.nxos_prefix_lists: &id003 + config: + - afi: ipv4 + prefix_lists: + - name: AllowPrefix + description: allows engineering IPv4 networks + entries: + - sequence: 10 + action: permit + prefix: 203.0.113.64/27 + + - sequence: 30 + action: permit + prefix: 203.0.113.96/27 + - name: AllowPrefix2Stub + description: allow other engineering IPv4 network + state: overridden + +# Task output +# ------------- +# before: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 192.0.2.0/23 +# eq: 24 +# - sequence: 20 +# action: permit +# prefix: 198.51.100.128/26 +# name: AllowPrefix +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +# +# commands: +# - "no ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24" +# - "ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27" +# - "ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27" +# - "no ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26" +# - "ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network" +# - "no ip prefix-list DenyPrefix" +# - "no ipv6 prefix-list AllowIPv6Prefix" +# +# after: +# - afi: ipv4 +# prefix_lists: +# - name: AllowPrefix +# description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 203.0.113.64/27 +# +# - sequence: 30 +# action: permit +# prefix: 203.0.113.96/27 +# - name: AllowPrefix2Stub +# description: allow other engineering IPv4 network +# +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27 +# ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27 +# ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network + +# Using deleted to delete a all prefix lists for an AFI + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24 +# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26 +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +- name: Delete all prefix-lists for an AFI + cisco.nxos.nxos_prefix_lists: + config: + - afi: ipv4 + state: deleted + register: result + +# Task output +# ------------- +# before: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 192.0.2.0/23 +# eq: 24 +# - sequence: 20 +# action: permit +# prefix: 198.51.100.128/26 +# name: AllowPrefix +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +# +# commands: +# - "no ip prefix-list AllowPrefix" +# - "no ip prefix-list DenyPrefix" +# +# after: +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +# +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +# Using deleted to delete a single prefix-list + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24 +# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26 +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +- name: Delete a single prefix-list + cisco.nxos.nxos_prefix_lists: + config: + - afi: ipv4 + prefix_lists: + - name: AllowPrefix + state: deleted + +# Task output +# ------------- +# before: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 192.0.2.0/23 +# eq: 24 +# - sequence: 20 +# action: permit +# prefix: 198.51.100.128/26 +# name: AllowPrefix +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +# +# commands: +# - "no ip prefix-list AllowPrefix" +# +# after: +# - afi: ipv4 +# prefix_lists: +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +# +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +# Using deleted to delete all prefix-lists from the device + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24 +# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26 +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +- name: Delete all prefix-lists + cisco.nxos.nxos_prefix_lists: + state: deleted + +# Task output +# ------------- +# before: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 192.0.2.0/23 +# eq: 24 +# - sequence: 20 +# action: permit +# prefix: 198.51.100.128/26 +# name: AllowPrefix +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +# +# commands: +# - "no ip prefix-list AllowPrefix" +# - "no ip prefix-list DenyPrefix" +# - "no ipv6 prefix-list AllowIPv6Prefix" +# +# after: [] +# +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list' +# nxos-9k-rdo# + +# Using rendered + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_prefix_lists: &id001 + config: + - afi: ipv4 + prefix_lists: + - name: AllowPrefix + description: allows engineering IPv4 networks + entries: + - sequence: 10 + action: permit + prefix: 192.0.2.0/23 + eq: 24 + - sequence: 20 + action: permit + prefix: 198.51.100.128/26 + - name: DenyPrefix + description: denies lab IPv4 networks + entries: + - sequence: 20 + action: deny + prefix: 203.0.113.0/24 + le: 25 + + - afi: ipv6 + prefix_lists: + - name: AllowIPv6Prefix + description: allows engineering IPv6 networks + entries: + - sequence: 8 + action: permit + prefix: "2001:db8:400::/38" + - sequence: 20 + action: permit + prefix: "2001:db8:8000::/35" + le: 37 + state: rendered + +# Task Output (redacted) +# ----------------------- +# rendered: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 192.0.2.0/23 +# eq: 24 +# - sequence: 20 +# action: permit +# prefix: 198.51.100.128/26 +# name: AllowPrefix +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix + +# Using parsed + +# parsed.cfg +# ------------ +# ip prefix-list AllowPrefix description allows engineering IPv4 networks +# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24 +# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26 +# ip prefix-list DenyPrefix description denies lab IPv4 networks +# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25 +# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks +# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38 +# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37 + +- name: Parse externally provided prefix-lists configuration + register: result + cisco.nxos.nxos_prefix_lists: + running_config: "{{ lookup('file', './parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# - afi: ipv4 +# prefix_lists: +# - description: allows engineering IPv4 networks +# entries: +# - sequence: 10 +# action: permit +# prefix: 192.0.2.0/23 +# eq: 24 +# - sequence: 20 +# action: permit +# prefix: 198.51.100.128/26 +# name: AllowPrefix +# - description: denies lab IPv4 networks +# entries: +# - sequence: 20 +# action: deny +# prefix: 203.0.113.0/24 +# le: 25 +# name: DenyPrefix +# +# - afi: ipv6 +# prefix_lists: +# - description: allows engineering IPv6 networks +# entries: +# - sequence: 8 +# action: permit +# prefix: "2001:db8:400::/38" +# - sequence: 20 +# action: permit +# prefix: "2001:db8:8000::/35" +# le: 37 +# name: AllowIPv6Prefix +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.prefix_lists.prefix_lists import ( + Prefix_listsArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.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, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Prefix_lists(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py new file mode 100644 index 00000000..51d0d70a --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py @@ -0,0 +1,89 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_reboot +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Reboot a network device. +description: +- Reboot a network device. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Tested against Cisco MDS NX-OS 9.2(1) +- The module will fail due to timeout issues, but the reboot will be performed anyway. +options: + confirm: + description: + - Safeguard boolean. Set to true if you're sure you want to reboot. + required: false + default: false + type: bool +""" + +EXAMPLES = """ +- cisco.nxos.nxos_reboot: + confirm: true +""" + +RETURN = """ +rebooted: + description: Whether the device was instructed to reboot. + returned: success + type: bool + sample: true +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import load_config + + +def reboot(module): + cmds = "terminal dont-ask ; reload" + opts = {"ignore_timeout": True} + load_config(module, cmds, False, opts) + + +def main(): + argument_spec = dict(confirm=dict(default=False, type="bool")) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = dict(changed=False, warnings=warnings) + + if module.params["confirm"]: + if not module.check_mode: + reboot(module) + results["changed"] = True + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py new file mode 100644 index 00000000..4b26919d --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_rollback +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Set a checkpoint or rollback to a checkpoint. +description: +- This module offers the ability to set a configuration checkpoint file or rollback + to a configuration checkpoint file on Cisco NXOS switches. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- Sometimes C(transport=nxapi) may cause a timeout error. +options: + checkpoint_file: + description: + - Name of checkpoint file to create. Mutually exclusive with rollback_to. + type: str + rollback_to: + description: + - Name of checkpoint file to rollback to. Mutually exclusive with checkpoint_file. + type: str +""" + +EXAMPLES = """ +- cisco.nxos.nxos_rollback: + checkpoint_file: backup.cfg + username: '{{ un }}' + password: '{{ pwd }}' + host: '{{ inventory_hostname }}' +- cisco.nxos.nxos_rollback: + rollback_to: backup.cfg + username: '{{ un }}' + password: '{{ pwd }}' + host: '{{ inventory_hostname }}' +""" + +RETURN = """ +filename: + description: The filename of the checkpoint/rollback file. + returned: success + type: str + sample: 'backup.cfg' +status: + description: Which operation took place and whether it was successful. + returned: success + type: str + sample: 'rollback executed' +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands + + +def checkpoint(filename, module): + commands = [ + {"command": "terminal dont-ask", "output": "text"}, + {"command": "checkpoint file %s" % filename, "output": "text"}, + ] + run_commands(module, commands) + + +def rollback(filename, module): + commands = [ + { + "command": "rollback running-config file %s" % filename, + "output": "text", + }, + ] + run_commands(module, commands) + + +def main(): + argument_spec = dict(checkpoint_file=dict(required=False), rollback_to=dict(required=False)) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[["checkpoint_file", "rollback_to"]], + supports_check_mode=False, + ) + + checkpoint_file = module.params["checkpoint_file"] + rollback_to = module.params["rollback_to"] + + status = None + filename = None + changed = False + + if checkpoint_file: + checkpoint(checkpoint_file, module) + status = "checkpoint file created" + elif rollback_to: + rollback(rollback_to, module) + status = "rollback executed" + changed = True + filename = rollback_to or checkpoint_file + + module.exit_json(changed=changed, status=status, filename=filename) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py new file mode 100644 index 00000000..111cc4da --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py @@ -0,0 +1,1648 @@ +#!/usr/bin/python +# -*- 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 module file for nxos_route_maps +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_route_maps +short_description: Route Maps resource module. +description: +- This module manages route maps configuration on devices running Cisco NX-OS. +version_added: 2.2.0 +notes: +- Tested against NX-OS 9.3.6. +- Unsupported for Cisco MDS +- This module works with connection C(network_cli) and C(httpapi). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^route-map'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A list of route-map configuration. + type: list + elements: dict + suboptions: + route_map: + description: Route-map name. + type: str + entries: + description: List of entries (identified by sequence number) for this route-map. + type: list + elements: dict + suboptions: + sequence: + description: Sequence to insert to/delete from existing route-map entry. + type: int + action: + description: Route map denies or permits set operations. + type: str + choices: ["deny", "permit"] + continue_sequence: + description: Continue on a different entry within the route-map. + type: int + description: + description: Description of the route-map. + type: str + match: + description: Match values from routing table. + type: dict + suboptions: + as_number: + description: Match BGP peer AS number. + type: dict + suboptions: + asn: + description: AS number. + type: list + elements: str + as_path_list: + description: AS path access list name. + type: list + elements: str + as_path: + description: Match BGP AS path access-list. + type: list + elements: str + community: + description: Match BGP community list. + type: dict + suboptions: + community_list: + description: Community list. + type: list + elements: str + exact_match: + description: Do exact matching of communities. + type: bool + evpn: + description: Match BGP EVPN Routes. + type: dict + suboptions: + route_types: + description: Match route type for evpn route. + type: list + elements: str + extcommunity: + description: Match BGP community list. + type: dict + suboptions: + extcommunity_list: + description: Extended Community list. + type: list + elements: str + exact_match: + description: Do exact matching of extended communities. + type: bool + interfaces: + description: Match first hop interface of route. + type: list + elements: str + ip: + description: Configure IP specific information. + type: dict + suboptions: &id001 + address: + description: Match address of route or match packet. + type: dict + suboptions: + access_list: + description: IP access-list name (for use in route-maps for PBR only). + type: str + prefix_lists: + description: Match entries of prefix-lists. + type: list + elements: str + multicast: + description: Match multicast attributes. + type: dict + suboptions: + source: + description: Multicast source address. + type: str + group: + description: + - Multicast Group prefix. + - Mutually exclusive with group_range. + type: dict + suboptions: + prefix: + description: IPv4 group prefix. + type: str + group_range: + description: + - Multicast Group address range. + - Mutually exclusive with group. + type: dict + suboptions: + first: + description: First Group address. + type: str + last: + description: Last Group address. + type: str + rp: + description: Rendezvous point. + type: dict + suboptions: + prefix: + description: IPv4 rendezvous prefix. + type: str + rp_type: + description: Multicast rendezvous point type. + type: str + choices: ["ASM", "Bidir"] + next_hop: + description: Match next-hop address of route. + type: dict + suboptions: + prefix_lists: + description: Match entries of prefix-lists. + type: list + elements: str + route_source: + description: Match advertising source address of route. + type: dict + suboptions: + prefix_lists: + description: Match entries of prefix-lists. + type: list + elements: str + ipv6: + description: Configure IPv6 specific information. + type: dict + suboptions: *id001 + mac_list: + description: Match entries of mac-lists. + type: list + elements: str + metric: + description: Match metric of route. + type: list + elements: int + ospf_area: + description: Match ospf area. + type: list + elements: int + route_types: + description: Match route-type of route. + type: list + elements: str + choices: ["external", "inter-area", "internal", "intra-area", "level-1", "level-2", "local", "nssa-external", "type-1", "type-2"] + source_protocol: + description: Match source protocol. + type: list + elements: str + tags: + description: Match tag of route. + type: list + elements: int + set: + description: Set values in destination routing protocol. + type: dict + suboptions: + as_path: + description: Prepend string for a BGP AS-path attribute. + type: dict + suboptions: + prepend: + description: Prepend to the AS-Path. + type: dict + suboptions: + as_number: + description: AS number. + type: list + elements: str + last_as: + description: Number of last-AS prepends. + type: int + tag: + description: Set the tag as an AS-path attribute. + type: bool + comm_list: + description: Set BGP community list (for deletion). + type: str + community: + description: Set BGP community attribute. + type: dict + suboptions: + additive: + description: Add to existing community. + type: bool + graceful_shutdown: + description: Graceful Shutdown (well-known community). + type: bool + internet: + description: Internet (well-known community). + type: bool + local_as: + description: Do not send outside local AS (well-known community). + type: bool + no_advertise: + description: Do not advertise to any peer (well-known community). + type: bool + no_export: + description: Do not export to next AS (well-known community). + type: bool + number: + description: "Community number aa:nn format" + type: list + elements: str + dampening: + description: Set BGP route flap dampening parameters. + type: dict + suboptions: + half_life: + description: Half-life time for the penalty. + type: int + start_reuse_route: + description: Value to start reusing a route. + type: int + start_suppress_route: + description: Value to start suppressing a route. + type: int + max_suppress_time: + description: Maximum suppress time for stable route. + type: int + distance: + description: Configure administrative distance. + type: dict + suboptions: + igp_ebgp_routes: + description: Administrative distance for IGP or EBGP routes + type: int + internal_routes: + description: Distance for internal routes. + type: int + local_routes: + description: Distance for local routes. + type: int + evpn: + description: Set BGP EVPN Routes. + type: dict + suboptions: + gateway_ip: + description: + - Set gateway IP for type 5 EVPN routes. + - Cannot set ip and use-nexthop in the same route-map sequence. + type: dict + suboptions: + ip: + description: Gateway IP address. + type: str + use_nexthop: + description: Use nexthop address as gateway IP. + type: bool + extcomm_list: + description: Set BGP extcommunity list (for deletion). + type: str + forwarding_address: + description: Set the forwarding address. + type: bool + null_interface: + description: Output Null interface. + type: str + ip: + description: Configure IP features. + type: dict + suboptions: &id002 + address: + description: Specify IP address. + type: dict + suboptions: + prefix_list: + description: Name of prefix list (Max Size 63). + type: str + precedence: + description: Set precedence field. + type: str + ipv6: + description: Configure IPv6 features. + type: dict + suboptions: *id002 + label_index: + description: Set Segment Routing (SR) label index of route. + type: int + level: + description: Where to import route. + type: str + choices: ["level-1", "level-1-2", "level-2"] + local_preference: + description: BGP local preference path attribute. + type: int + metric: + description: Set metric for destination routing protocol. + type: dict + suboptions: + bandwidth: + description: Metric value or Bandwidth in Kbits per second (Max Size 11). + type: int + igrp_delay_metric: + description: IGRP delay metric. + type: int + igrp_reliability_metric: + description: IGRP reliability metric where 255 is 100 percent reliable. + type: int + igrp_effective_bandwidth_metric: + description: IGRP Effective bandwidth metric (Loading) 255 is 100%. + type: int + igrp_mtu: + description: IGRP MTU of the path. + type: int + metric_type: + description: Type of metric for destination routing protocol. + type: str + choices: ["external", "internal", "type-1", "type-2"] + nssa_only: + description: OSPF NSSA Areas. + type: bool + origin: + description: BGP origin code. + type: str + choices: ["egp", "igp", "incomplete"] + path_selection: + description: Path selection criteria for BGP. + type: str + choices: ["all", "backup", "best2", "multipaths"] + tag: + description: Tag value for destination routing protocol. + type: int + weight: + description: BGP weight for routing table. + type: int + state: + description: + - The state the configuration should be left in. + - With state I(replaced), for the listed route-maps, + sequences that are in running-config but not in the task are negated. + - With state I(overridden), all route-maps that are in running-config but + not in the task are negated. + - Please refer to examples for more details. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# nxos-9k-rdo# show running-config | section "^route-map" +# nxos-9k-rdo# + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_route_maps: + config: + - route_map: rmap1 + entries: + - sequence: 10 + action: permit + description: rmap1-10-permit + match: + ip: + address: + access_list: acl_1 + as_path: Allow40 + as_number: + asn: 65564 + + - sequence: 20 + action: deny + description: rmap1-20-deny + match: + community: + community_list: + - BGPCommunity1 + - BGPCommunity2 + ip: + address: + prefix_lists: + - AllowPrefix1 + - AllowPrefix2 + set: + dampening: + half_life: 30 + start_reuse_route: 1500 + start_suppress_route: 10000 + max_suppress_time: 120 + + - route_map: rmap2 + entries: + - sequence: 20 + action: permit + description: rmap2-20-permit + continue_sequence: 40 + match: + ipv6: + address: + prefix_lists: AllowIPv6Prefix + interfaces: "{{ nxos_int1 }}" + set: + as_path: + prepend: + as_number: + - 65563 + - 65568 + - 65569 + comm_list: BGPCommunity + + - sequence: 40 + action: deny + description: rmap2-40-deny + match: + route_types: + - level-1 + - level-2 + tags: 2 + ip: + multicast: + rp: + prefix: 192.0.2.0/24 + rp_type: ASM + source: 203.0.113.0/24 + group_range: + first: 239.0.0.1 + last: 239.255.255.255 + state: merged + +# Task output +# ------------- +# before: [] +# +# commands: +# - "route-map rmap1 permit 10" +# - "match as-number 65564" +# - "match as-path Allow40" +# - "match ip address acl_1" +# - "description rmap1-10-permit" +# - "route-map rmap1 deny 20" +# - "match community BGPCommunity1 BGPCommunity2" +# - "match ip address prefix-list AllowPrefix1 AllowPrefix2" +# - "description rmap1-20-deny" +# - "set dampening 30 1500 10000 120" +# - "route-map rmap2 permit 20" +# - "match interface Ethernet1/1" +# - "match ipv6 address prefix-list AllowIPv6Prefix" +# - "set as-path prepend 65563 65568 65569" +# - "description rmap2-20-permit" +# - "continue 40" +# - "set comm-list BGPCommunity delete" +# - "route-map rmap2 deny 40" +# - "match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM" +# - "match route-type level-1 level-2" +# - "match tag 2" +# - "description rmap2-40-deny" +# +# after: +# - route_map: rmap1 +# entries: +# - action: permit +# description: rmap1-10-permit +# match: +# as_number: +# asn: +# - '65564' +# as_path: +# - Allow40 +# ip: +# address: +# access_list: acl_1 +# sequence: 10 +# +# - action: deny +# description: rmap1-20-deny +# match: +# community: +# community_list: +# - BGPCommunity1 +# - BGPCommunity2 +# ip: +# address: +# prefix_lists: +# - AllowPrefix1 +# - AllowPrefix2 +# sequence: 20 +# set: +# dampening: +# half_life: 30 +# max_suppress_time: 120 +# start_reuse_route: 1500 +# start_suppress_route: 10000 +# +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +# - action: deny +# description: rmap2-40-deny +# match: +# ip: +# multicast: +# group_range: +# first: 239.0.0.1 +# last: 239.255.255.255 +# rp: +# prefix: 192.0.2.0/24 +# rp_type: ASM +# source: 203.0.113.0/24 +# route_types: +# - level-1 +# - level-2 +# tags: +# - 2 +# sequence: 40 + +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section "^route-map" +# route-map rmap1 permit 10 +# match as-number 65564 +# match as-path Allow40 +# match ip address acl_1 +# description rmap1-10-permit +# route-map rmap1 deny 20 +# match community BGPCommunity1 BGPCommunity2 +# match ip address prefix-list AllowPrefix1 AllowPrefix2 +# description rmap1-20-deny +# set dampening 30 1500 10000 120 +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete +# route-map rmap2 deny 40 +# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM +# match route-type level-1 level-2 +# match tag 2 +# description rmap2-40-deny + +# Using replaced +# (for the listed route-map(s), sequences that are in running-config but not in the task are negated) + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section "^route-map" +# route-map rmap1 permit 10 +# match as-number 65564 +# match as-path Allow40 +# match ip address acl_1 +# description rmap1-10-permit +# route-map rmap1 deny 20 +# match community BGPCommunity1 BGPCommunity2 +# match ip address prefix-list AllowPrefix1 AllowPrefix2 +# description rmap1-20-deny +# set dampening 30 1500 10000 120 +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete +# route-map rmap2 deny 40 +# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM +# match route-type level-1 level-2 +# match tag 2 +# description rmap2-40-deny + +- name: Replace route-maps configurations of listed route-maps with provided configurations + cisco.nxos.nxos_route_maps: + config: + - route_map: rmap1 + entries: + - sequence: 20 + action: deny + description: rmap1-20-deny + match: + community: + community_list: + - BGPCommunity4 + - BGPCommunity5 + ip: + address: + prefix_lists: + - AllowPrefix1 + set: + community: + local_as: True + state: replaced + +# Task output +# ------------- +# before: +# - route_map: rmap1 +# entries: +# - action: permit +# description: rmap1-10-permit +# match: +# as_number: +# asn: +# - '65564' +# as_path: +# - Allow40 +# ip: +# address: +# access_list: acl_1 +# sequence: 10 +# +# - action: deny +# description: rmap1-20-deny +# match: +# community: +# community_list: +# - BGPCommunity1 +# - BGPCommunity2 +# ip: +# address: +# prefix_lists: +# - AllowPrefix1 +# - AllowPrefix2 +# sequence: 20 +# set: +# dampening: +# half_life: 30 +# max_suppress_time: 120 +# start_reuse_route: 1500 +# start_suppress_route: 10000 +# +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +# - action: deny +# description: rmap2-40-deny +# match: +# ip: +# multicast: +# group_range: +# first: 239.0.0.1 +# last: 239.255.255.255 +# rp: +# prefix: 192.0.2.0/24 +# rp_type: ASM +# source: 203.0.113.0/24 +# route_types: +# - level-1 +# - level-2 +# tags: +# - 2 +# sequence: 40 +# +# commands: +# - no route-map rmap1 permit 10 +# - route-map rmap1 deny 20 +# - no match community BGPCommunity1 BGPCommunity2 +# - match community BGPCommunity4 BGPCommunity5 +# - no match ip address prefix-list AllowPrefix1 AllowPrefix2 +# - match ip address prefix-list AllowPrefix1 +# - no set dampening 30 1500 10000 120 +# - set community local-AS +# +# after: +# - route_map: rmap1 +# entries: +# - sequence: 20 +# action: deny +# description: rmap1-20-deny +# match: +# community: +# community_list: +# - BGPCommunity4 +# - BGPCommunity5 +# ip: +# address: +# prefix_lists: +# - AllowPrefix1 +# set: +# community: +# local_as: True +# +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +# - action: deny +# description: rmap2-40-deny +# match: +# ip: +# multicast: +# group_range: +# first: 239.0.0.1 +# last: 239.255.255.255 +# rp: +# prefix: 192.0.2.0/24 +# rp_type: ASM +# source: 203.0.113.0/24 +# route_types: +# - level-1 +# - level-2 +# tags: +# - 2 +# sequence: 40 +# + +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section "^route-map" +# route-map rmap1 deny 20 +# description rmap1-20-deny +# match community BGPCommunity4 BGPCommunity5 +# match ip address prefix-list AllowPrefix1 +# set community local-AS +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete +# route-map rmap2 deny 40 +# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM +# match route-type level-1 level-2 +# match tag 2 +# description rmap2-40-deny + +# Using overridden + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section "^route-map" +# route-map rmap1 permit 10 +# match as-number 65564 +# match as-path Allow40 +# match ip address acl_1 +# description rmap1-10-permit +# route-map rmap1 deny 20 +# match community BGPCommunity1 BGPCommunity2 +# match ip address prefix-list AllowPrefix1 AllowPrefix2 +# description rmap1-20-deny +# set dampening 30 1500 10000 120 +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete +# route-map rmap2 deny 40 +# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM +# match route-type level-1 level-2 +# match tag 2 +# description rmap2-40-deny + +- name: Override all route-maps configuration with provided configuration + cisco.nxos.nxos_route_maps: + config: + - route_map: rmap1 + entries: + - sequence: 20 + action: deny + description: rmap1-20-deny + match: + community: + community_list: + - BGPCommunity4 + - BGPCommunity5 + ip: + address: + prefix_lists: + - AllowPrefix1 + set: + community: + local_as: True + state: overridden + +# Task output +# ------------- +# before: +# - route_map: rmap1 +# entries: +# - action: permit +# description: rmap1-10-permit +# match: +# as_number: +# asn: +# - '65564' +# as_path: +# - Allow40 +# ip: +# address: +# access_list: acl_1 +# sequence: 10 +# +# - action: deny +# description: rmap1-20-deny +# match: +# community: +# community_list: +# - BGPCommunity1 +# - BGPCommunity2 +# ip: +# address: +# prefix_lists: +# - AllowPrefix1 +# - AllowPrefix2 +# sequence: 20 +# set: +# dampening: +# half_life: 30 +# max_suppress_time: 120 +# start_reuse_route: 1500 +# start_suppress_route: 10000 +# +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +# - action: deny +# description: rmap2-40-deny +# match: +# ip: +# multicast: +# group_range: +# first: 239.0.0.1 +# last: 239.255.255.255 +# rp: +# prefix: 192.0.2.0/24 +# rp_type: ASM +# source: 203.0.113.0/24 +# route_types: +# - level-1 +# - level-2 +# tags: +# - 2 +# sequence: 40 +# +# commands: +# - no route-map rmap1 permit 10 +# - route-map rmap1 deny 20 +# - no match community BGPCommunity1 BGPCommunity2 +# - match community BGPCommunity4 BGPCommunity5 +# - no match ip address prefix-list AllowPrefix1 AllowPrefix2 +# - match ip address prefix-list AllowPrefix1 +# - no set dampening 30 1500 10000 120 +# - set community local-AS +# - no route-map rmap2 permit 20 +# - no route-map rmap2 deny 40 +# +# after: +# - route_map: rmap1 +# entries: +# - sequence: 20 +# action: deny +# description: rmap1-20-deny +# match: +# community: +# community_list: +# - BGPCommunity4 +# - BGPCommunity5 +# ip: +# address: +# prefix_lists: +# - AllowPrefix1 +# set: +# community: +# local_as: True +# +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^route-map" +# route-map rmap1 deny 20 +# description rmap1-20-deny +# match community BGPCommunity4 BGPCommunity5 +# match ip address prefix-list AllowPrefix1 +# set community local-AS + +# Using deleted to delete a single route-map + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section "^route-map" +# route-map rmap1 permit 10 +# match as-number 65564 +# match as-path Allow40 +# match ip address acl_1 +# description rmap1-10-permit +# route-map rmap1 deny 20 +# match community BGPCommunity1 BGPCommunity2 +# match ip address prefix-list AllowPrefix1 AllowPrefix2 +# description rmap1-20-deny +# set dampening 30 1500 10000 120 +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete +# route-map rmap2 deny 40 +# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM +# match route-type level-1 level-2 +# match tag 2 +# description rmap2-40-deny + +- name: Delete single route-map + cisco.nxos.nxos_route_maps: + config: + - route_map: rmap1 + state: deleted + +# Task output +# ------------- +# before: +# - route_map: rmap1 +# entries: +# - action: permit +# description: rmap1-10-permit +# match: +# as_number: +# asn: +# - '65564' +# as_path: +# - Allow40 +# ip: +# address: +# access_list: acl_1 +# sequence: 10 +# +# - action: deny +# description: rmap1-20-deny +# match: +# community: +# community_list: +# - BGPCommunity1 +# - BGPCommunity2 +# ip: +# address: +# prefix_lists: +# - AllowPrefix1 +# - AllowPrefix2 +# sequence: 20 +# set: +# dampening: +# half_life: 30 +# max_suppress_time: 120 +# start_reuse_route: 1500 +# start_suppress_route: 10000 +# +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +# - action: deny +# description: rmap2-40-deny +# match: +# ip: +# multicast: +# group_range: +# first: 239.0.0.1 +# last: 239.255.255.255 +# rp: +# prefix: 192.0.2.0/24 +# rp_type: ASM +# source: 203.0.113.0/24 +# route_types: +# - level-1 +# - level-2 +# tags: +# - 2 +# sequence: 40 +# +# commands: +# - no route-map rmap1 permit 10 +# - no route-map rmap1 deny 20 +# +# after: +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +# - action: deny +# description: rmap2-40-deny +# match: +# ip: +# multicast: +# group_range: +# first: 239.0.0.1 +# last: 239.255.255.255 +# rp: +# prefix: 192.0.2.0/24 +# rp_type: ASM +# source: 203.0.113.0/24 +# route_types: +# - level-1 +# - level-2 +# tags: +# - 2 +# sequence: 40 +# +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^route-map" +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete +# route-map rmap2 deny 40 +# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM +# match route-type level-1 level-2 +# match tag 2 +# description rmap2-40-deny + +# Using deleted to delete all route-maps from the device running-config + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section "^route-map" +# route-map rmap1 permit 10 +# match as-number 65564 +# match as-path Allow40 +# match ip address acl_1 +# description rmap1-10-permit +# route-map rmap1 deny 20 +# match community BGPCommunity1 BGPCommunity2 +# match ip address prefix-list AllowPrefix1 AllowPrefix2 +# description rmap1-20-deny +# set dampening 30 1500 10000 120 +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete +# route-map rmap2 deny 40 +# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM +# match route-type level-1 level-2 +# match tag 2 +# description rmap2-40-deny + +- name: Delete all route-maps + cisco.nxos.nxos_route_maps: + state: deleted + +# Task output +# ------------- +# before: +# - route_map: rmap1 +# entries: +# - action: permit +# description: rmap1-10-permit +# match: +# as_number: +# asn: +# - '65564' +# as_path: +# - Allow40 +# ip: +# address: +# access_list: acl_1 +# sequence: 10 +# +# - action: deny +# description: rmap1-20-deny +# match: +# community: +# community_list: +# - BGPCommunity1 +# - BGPCommunity2 +# ip: +# address: +# prefix_lists: +# - AllowPrefix1 +# - AllowPrefix2 +# sequence: 20 +# set: +# dampening: +# half_life: 30 +# max_suppress_time: 120 +# start_reuse_route: 1500 +# start_suppress_route: 10000 +# +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +# - action: deny +# description: rmap2-40-deny +# match: +# ip: +# multicast: +# group_range: +# first: 239.0.0.1 +# last: 239.255.255.255 +# rp: +# prefix: 192.0.2.0/24 +# rp_type: ASM +# source: 203.0.113.0/24 +# route_types: +# - level-1 +# - level-2 +# tags: +# - 2 +# sequence: 40 +# +# commands: +# - no route-map rmap1 permit 10 +# - no route-map rmap1 deny 20 +# - no route-map rmap2 permit 20 +# - no route-map rmap2 deny 40 +# +# after: [] +# +# After state: +# ------------ +# nxos-9k-rdo# sh running-config | section "^route-map" + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_route_maps: + config: + - route_map: rmap1 + entries: + - sequence: 10 + action: permit + description: rmap1-10-permit + match: + ip: + address: + access_list: acl_1 + as_path: Allow40 + as_number: + asn: 65564 + + - sequence: 20 + action: deny + description: rmap1-20-deny + match: + community: + community_list: + - BGPCommunity1 + - BGPCommunity2 + ip: + address: + prefix_lists: + - AllowPrefix1 + - AllowPrefix2 + set: + dampening: + half_life: 30 + start_reuse_route: 1500 + start_suppress_route: 10000 + max_suppress_time: 120 + + - route_map: rmap2 + entries: + - sequence: 20 + action: permit + description: rmap2-20-permit + continue_sequence: 40 + match: + ipv6: + address: + prefix_lists: AllowIPv6Prefix + interfaces: "{{ nxos_int1 }}" + set: + as_path: + prepend: + as_number: + - 65563 + - 65568 + - 65569 + comm_list: BGPCommunity + + - sequence: 40 + action: deny + description: rmap2-40-deny + match: + route_types: + - level-1 + - level-2 + tags: 2 + ip: + multicast: + rp: + prefix: 192.0.2.0/24 + rp_type: ASM + source: 203.0.113.0/24 + group_range: + first: 239.0.0.1 + last: 239.255.255.255 + state: rendered + +# Task Output (redacted) +# ----------------------- +# rendered: +# - "route-map rmap1 permit 10" +# - "match as-number 65564" +# - "match as-path Allow40" +# - "match ip address acl_1" +# - "description rmap1-10-permit" +# - "route-map rmap1 deny 20" +# - "match community BGPCommunity1 BGPCommunity2" +# - "match ip address prefix-list AllowPrefix1 AllowPrefix2" +# - "description rmap1-20-deny" +# - "set dampening 30 1500 10000 120" +# - "route-map rmap2 permit 20" +# - "match interface Ethernet1/1" +# - "match ipv6 address prefix-list AllowIPv6Prefix" +# - "set as-path prepend 65563 65568 65569" +# - "description rmap2-20-permit" +# - "continue 40" +# - "set comm-list BGPCommunity delete" +# - "route-map rmap2 deny 40" +# - "match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM" +# - "match route-type level-1 level-2" +# - "match tag 2" +# - "description rmap2-40-deny" + +# Using parsed + +# parsed.cfg +# ------------ +# route-map rmap1 permit 10 +# match as-number 65564 +# match as-path Allow40 +# match ip address acl_1 +# description rmap1-10-permit +# route-map rmap1 deny 20 +# match community BGPCommunity1 BGPCommunity2 +# match ip address prefix-list AllowPrefix1 AllowPrefix2 +# description rmap1-20-deny +# set dampening 30 1500 10000 120 +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete +# route-map rmap2 deny 40 +# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM +# match route-type level-1 level-2 +# match tag 2 +# description rmap2-40-deny + +- name: Parse externally provided route-maps configuration + cisco.nxos.nxos_route_maps: + running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# - route_map: rmap1 +# entries: +# - action: permit +# description: rmap1-10-permit +# match: +# as_number: +# asn: +# - '65564' +# as_path: +# - Allow40 +# ip: +# address: +# access_list: acl_1 +# sequence: 10 +# +# - action: deny +# description: rmap1-20-deny +# match: +# community: +# community_list: +# - BGPCommunity1 +# - BGPCommunity2 +# ip: +# address: +# prefix_lists: +# - AllowPrefix1 +# - AllowPrefix2 +# sequence: 20 +# set: +# dampening: +# half_life: 30 +# max_suppress_time: 120 +# start_reuse_route: 1500 +# start_suppress_route: 10000 +# +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +# - action: deny +# description: rmap2-40-deny +# match: +# ip: +# multicast: +# group_range: +# first: 239.0.0.1 +# last: 239.255.255.255 +# rp: +# prefix: 192.0.2.0/24 +# rp_type: ASM +# source: 203.0.113.0/24 +# route_types: +# - level-1 +# - level-2 +# tags: +# - 2 +# sequence: 40 + +# Using gathered + +# Existing route-map config +# --------------------------- +# nxos-9k-rdo# show running-config | section "^route-map" +# route-map rmap1 permit 10 +# match as-number 65564 +# match as-path Allow40 +# match ip address acl_1 +# description rmap1-10-permit +# route-map rmap2 permit 20 +# match interface Ethernet1/1 +# match ipv6 address prefix-list AllowIPv6Prefix +# set as-path prepend 65563 65568 65569 +# description rmap2-20-permit +# continue 40 +# set comm-list BGPCommunity delete + +- name: Gather route-maps facts using gathered + cisco.nxos.nxos_route_maps: + state: gathered + +# gathered: +# - route_map: rmap1 +# entries: +# - action: permit +# description: rmap1-10-permit +# match: +# as_number: +# asn: +# - '65564' +# as_path: +# - Allow40 +# ip: +# address: +# access_list: acl_1 +# sequence: 10 +# +# - route_map: rmap2 +# entries: +# - action: permit +# continue_sequence: 40 +# description: rmap2-20-permit +# match: +# interfaces: +# - Ethernet1/1 +# ipv6: +# address: +# prefix_lists: +# - AllowIPv6Prefix +# sequence: 20 +# set: +# as_path: +# prepend: +# as_number: +# - '65563' +# - '65568' +# - '65569' +# comm_list: BGPCommunity +# +""" + +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: + - "route-map rmap1 permit 10" + - "match as-number 65564" + - "match as-path Allow40" + - "match ip address acl_1" + - "description rmap1-10-permit" + - "route-map rmap1 deny 20" + - "match community BGPCommunity1 BGPCommunity2" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.route_maps.route_maps import ( + Route_mapsArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.route_maps.route_maps import ( + Route_maps, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Route_mapsArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Route_maps(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py new file mode 100644 index 00000000..7d7ab4db --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py @@ -0,0 +1,396 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_rpm +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Install patch or feature rpms on Cisco NX-OS devices. +description: +- Install software maintenance upgrade (smu) RPMS and 3rd party RPMS on Cisco NX-OS + devices. +version_added: 1.0.0 +author: Sai Chintalapudi (@saichint) +notes: +- Tested against NXOSv 7.0(3)I2(5), 7.0(3)I4(6), 7.0(3)I5(3), 7.0(3)I6(1), 7.0(3)I7(3) +- Unsupported for Cisco MDS +- For patches, the minimum platform version needed is 7.0(3)I2(5) +- For feature rpms, the minimum platform version needed is 7.0(3)I6(1) +- The module manages the entire RPM lifecycle (Add, activate, commit, deactivate, + remove) +- For reload patches, this module is NOT idempotent until the patch is committed. +options: + pkg: + description: + - Name of the RPM package. + type: str + file_system: + description: + - The remote file system of the device. If omitted, devices that support a file_system + parameter will use their default values. + default: bootflash + type: str + aggregate: + description: + - List of RPM/patch definitions. + type: list + elements: dict + suboptions: + pkg: + description: + - Name of the RPM package. + required: True + type: str + file_system: + description: + - The remote file system of the device. If omitted, devices that support a file_system + parameter will use their default values. + type: str + state: + description: + - If the state is present, the rpm will be installed, If the state is absent, + it will be removed. + choices: + - present + - absent + type: str + state: + description: + - If the state is present, the rpm will be installed, If the state is absent, + it will be removed. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- cisco.nxos.nxos_rpm: + pkg: nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000.rpm +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["install add bootflash:nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000.rpm forced", + "install activate nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000 forced", + "install commit nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000"] +""" + + +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_default_spec, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module): + iteration = 0 + cmds = [{"command": command, "output": "text"}] + + while iteration < 10: + body = run_commands(module, cmds)[0] + if body: + return body + else: + time.sleep(2) + iteration += 1 + + +def remote_file_exists(module, dst, file_system): + command = "dir {0}:/{1}".format(file_system, dst) + body = execute_show_command(command, module) + if "No such file" in body: + return False + return True + + +def config_cmd_operation(module, cmd): + iteration = 0 + while iteration < 10: + msg = load_config(module, [cmd], True) + if msg: + if ( + "another install operation is in progress" in msg[0].lower() + or "failed" in msg[0].lower() + ): + time.sleep(2) + iteration += 1 + else: + return + else: + return + + +def validate_operation(module, show_cmd, cfg_cmd, pkg, pkg_not_present): + iteration = 0 + while iteration < 10: + body = execute_show_command(show_cmd, module) + if pkg_not_present: + if pkg not in body: + return + else: + if pkg in body: + return + time.sleep(2) + iteration += 1 + + err = 'Operation "{0}" Failed'.format(cfg_cmd) + module.fail_json(msg=err) + + +def add_operation(module, show_cmd, file_system, full_pkg, pkg): + cmd = "install add {0}:{1}".format(file_system, full_pkg) + config_cmd_operation(module, cmd) + validate_operation(module, show_cmd, cmd, pkg, False) + return cmd + + +def activate_operation(module, show_cmd, pkg): + cmd = "install activate {0} forced".format(pkg) + config_cmd_operation(module, cmd) + validate_operation(module, show_cmd, cmd, pkg, False) + return cmd + + +def activate_reload(module, pkg, flag): + iteration = 0 + if flag: + cmd = "install activate {0} forced".format(pkg) + else: + cmd = "install deactivate {0} forced".format(pkg) + opts = {"ignore_timeout": True} + while iteration < 10: + msg = load_config(module, [cmd], True, opts) + if msg: + if isinstance(msg[0], int): + if msg[0] == -32603: + return cmd + elif isinstance(msg[0], str): + if "socket is closed" in msg[0].lower(): + return cmd + if ( + "another install operation is in progress" in msg[0].lower() + or "failed" in msg[0].lower() + ): + time.sleep(2) + iteration += 1 + + +def commit_operation(module, show_cmd, pkg, flag): + cmd = "install commit {0}".format(pkg) + config_cmd_operation(module, cmd) + validate_operation(module, show_cmd, cmd, pkg, flag) + return cmd + + +def deactivate_operation(module, show_cmd, pkg, flag): + cmd = "install deactivate {0} forced".format(pkg) + config_cmd_operation(module, cmd) + validate_operation(module, show_cmd, cmd, pkg, flag) + return cmd + + +def terminal_operation(module, config): + if config: + cmd = "terminal dont-ask" + else: + cmd = "no terminal dont-ask" + config_cmd_operation(module, cmd) + return cmd + + +def remove_operation(module, show_cmd, pkg): + commands = [] + commands.append(terminal_operation(module, True)) + cmd = "install remove {0} forced".format(pkg) + config_cmd_operation(module, cmd) + validate_operation(module, show_cmd, cmd, pkg, True) + commands.append(cmd) + commands.append(terminal_operation(module, False)) + return commands + + +def install_remove_rpm(module, full_pkg, file_system, state): + commands = [] + reload_patch = False + + splitted_pkg = full_pkg.split(".") + pkg = ".".join(splitted_pkg[0:-1]) + + show_inactive = "show install inactive" + show_active = "show install active" + show_commit = "show install committed" + show_patches = "show install patches" + show_pkg_info = "show install pkg-info {0}".format(pkg) + + if state == "present": + inactive_body = execute_show_command(show_inactive, module) + active_body = execute_show_command(show_active, module) + + if pkg not in inactive_body and pkg not in active_body: + commands.append(add_operation(module, show_inactive, file_system, full_pkg, pkg)) + + patch_type_body = execute_show_command(show_pkg_info, module) + if patch_type_body and "Patch Type : reload" in patch_type_body: + # This is reload smu/patch rpm + reload_patch = True + + if pkg not in active_body: + if reload_patch: + commands.append(activate_reload(module, pkg, True)) + return commands + else: + commands.append(activate_operation(module, show_active, pkg)) + + commit_body = execute_show_command(show_commit, module) + if pkg not in commit_body: + patch_body = execute_show_command(show_patches, module) + if pkg in patch_body: + # This is smu/patch rpm + commands.append(commit_operation(module, show_commit, pkg, False)) + else: + err = 'Operation "install activate {0} forced" Failed'.format(pkg) + module.fail_json(msg=err) + + else: + commit_body = execute_show_command(show_commit, module) + active_body = execute_show_command(show_active, module) + + patch_type_body = execute_show_command(show_pkg_info, module) + if patch_type_body and "Patch Type : reload" in patch_type_body: + # This is reload smu/patch rpm + reload_patch = True + + if pkg in commit_body and pkg in active_body: + if reload_patch: + commands.append(activate_reload(module, pkg, False)) + return commands + else: + commands.append(deactivate_operation(module, show_active, pkg, True)) + commit_body = execute_show_command(show_commit, module) + if pkg in commit_body: + # This is smu/patch rpm + commands.append(commit_operation(module, show_commit, pkg, True)) + commands.extend(remove_operation(module, show_inactive, pkg)) + + elif pkg in commit_body: + # This is smu/patch rpm + commands.append(commit_operation(module, show_commit, pkg, True)) + commands.extend(remove_operation(module, show_inactive, pkg)) + + elif pkg in active_body: + # This is smu/patch rpm + if reload_patch: + commands.append(activate_reload(module, pkg, False)) + return commands + else: + commands.append(deactivate_operation(module, show_inactive, pkg, False)) + commands.extend(remove_operation(module, show_inactive, pkg)) + + else: + inactive_body = execute_show_command(show_inactive, module) + if pkg in inactive_body: + commands.extend(remove_operation(module, show_inactive, pkg)) + + return commands + + +def main(): + element_spec = dict( + pkg=dict(type="str"), + file_system=dict(type="str", default="bootflash"), + state=dict(choices=["absent", "present"], default="present"), + ) + + aggregate_spec = deepcopy(element_spec) + aggregate_spec["pkg"] = dict(required=True) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict(aggregate=dict(type="list", elements="dict", options=aggregate_spec)) + + argument_spec.update(element_spec) + required_one_of = [["pkg", "aggregate"]] + mutually_exclusive = [["pkg", "aggregate"]] + + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=False, + ) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + aggregate = module.params.get("aggregate") + objects = [] + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + objects.append(d) + else: + objects.append( + { + "pkg": module.params["pkg"], + "file_system": module.params["file_system"], + "state": module.params["state"], + }, + ) + + for obj in objects: + if obj["state"] == "present": + remote_exists = remote_file_exists(module, obj["pkg"], file_system=obj["file_system"]) + + if not remote_exists: + module.fail_json(msg="The requested package doesn't exist on the device") + + cmds = install_remove_rpm(module, obj["pkg"], obj["file_system"], obj["state"]) + + if cmds: + results["changed"] = True + results["commands"].extend(cmds) + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py new file mode 100644 index 00000000..9697a128 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py @@ -0,0 +1,413 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = """ +module: nxos_snapshot +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manage snapshots of the running states of selected features. +description: +- Create snapshots of the running states of selected features, add new show commands + for snapshot creation, delete and compare existing snapshots. +version_added: 1.0.0 +author: +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- C(transport=cli) may cause timeout errors. +- The C(element_key1) and C(element_key2) parameter specify the tags used to distinguish + among row entries. In most cases, only the element_key1 parameter needs to specified + to be able to distinguish among row entries. +- C(action=compare) will always store a comparison report on a local file. +options: + action: + description: + - Define what snapshot action the module would perform. + required: true + choices: + - add + - compare + - create + - delete + - delete_all + type: str + snapshot_name: + description: + - Snapshot name, to be used when C(action=create) or C(action=delete). + type: str + description: + description: + - Snapshot description to be used when C(action=create). + type: str + snapshot1: + description: + - First snapshot to be used when C(action=compare). + type: str + snapshot2: + description: + - Second snapshot to be used when C(action=compare). + type: str + comparison_results_file: + description: + - Name of the file where snapshots comparison will be stored when C(action=compare). + type: str + compare_option: + description: + - Snapshot options to be used when C(action=compare). + choices: + - summary + - ipv4routes + - ipv6routes + type: str + section: + description: + - Used to name the show command output, to be used when C(action=add). + type: str + show_command: + description: + - Specify a new show command, to be used when C(action=add). + type: str + row_id: + description: + - Specifies the tag of each row entry of the show command's XML output, to be + used when C(action=add). + type: str + element_key1: + description: + - Specify the tags used to distinguish among row entries, to be used when C(action=add). + type: str + element_key2: + description: + - Specify the tags used to distinguish among row entries, to be used when C(action=add). + type: str + save_snapshot_locally: + description: + - Specify to locally store a new created snapshot, to be used when C(action=create). + type: bool + default: no + path: + description: + - Specify the path of the file where new created snapshot or snapshots comparison + will be stored, to be used when C(action=create) and C(save_snapshot_locally=true) + or C(action=compare). + default: ./ + type: str +""" + +EXAMPLES = """ +# Create a snapshot and store it locally +- cisco.nxos.nxos_snapshot: + action: create + snapshot_name: test_snapshot + description: Done with Ansible + save_snapshot_locally: true + path: /home/user/snapshots/ + +# Delete a snapshot +- cisco.nxos.nxos_snapshot: + action: delete + snapshot_name: test_snapshot + +# Delete all existing snapshots +- cisco.nxos.nxos_snapshot: + action: delete_all + +# Add a show command for snapshots creation +- cisco.nxos.nxos_snapshot: + section: myshow + show_command: show ip interface brief + row_id: ROW_intf + element_key1: intf-name + +# Compare two snapshots +- cisco.nxos.nxos_snapshot: + action: compare + snapshot1: pre_snapshot + snapshot2: post_snapshot + comparison_results_file: compare_snapshots.txt + compare_option: summary + path: ../snapshot_reports/ +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: verbose mode + type: list + sample: ["snapshot create post_snapshot Post-snapshot"] +""" + +import os +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module): + command = [{"command": command, "output": "text"}] + + return run_commands(module, command) + + +def get_existing(module): + existing = [] + command = "show snapshots" + + body = execute_show_command(command, module)[0] + if body: + split_body = body.splitlines() + snapshot_regex = ( + r"(?P<name>\S+)\s+(?P<date>\w+\s+\w+\s+\d+\s+\d+" + r":\d+:\d+\s+\d+)\s+(?P<description>.*)" + ) + for snapshot in split_body: + temp = {} + try: + match_snapshot = re.match(snapshot_regex, snapshot, re.DOTALL) + snapshot_group = match_snapshot.groupdict() + temp["name"] = snapshot_group["name"] + temp["date"] = snapshot_group["date"] + temp["description"] = snapshot_group["description"] + existing.append(temp) + except AttributeError: + pass + + return existing + + +def action_create(module, existing_snapshots): + commands = list() + exist = False + for snapshot in existing_snapshots: + if module.params["snapshot_name"] == snapshot["name"]: + exist = True + + if exist is False: + commands.append( + "snapshot create {0} {1}".format( + module.params["snapshot_name"], + module.params["description"], + ), + ) + + return commands + + +def action_add(module, existing_snapshots): + commands = list() + command = "show snapshot sections" + sections = [] + body = execute_show_command(command, module)[0] + + if body: + section_regex = r".*\[(?P<section>\S+)\].*" + split_body = body.split("\n\n") + for section in split_body: + temp = {} + for line in section.splitlines(): + try: + match_section = re.match(section_regex, section, re.DOTALL) + temp["section"] = match_section.groupdict()["section"] + except (AttributeError, KeyError): + pass + + if "show command" in line: + temp["show_command"] = line.split("show command: ")[1] + elif "row id" in line: + temp["row_id"] = line.split("row id: ")[1] + elif "key1" in line: + temp["element_key1"] = line.split("key1: ")[1] + elif "key2" in line: + temp["element_key2"] = line.split("key2: ")[1] + + if temp: + sections.append(temp) + + proposed = { + "section": module.params["section"], + "show_command": module.params["show_command"], + "row_id": module.params["row_id"], + "element_key1": module.params["element_key1"], + "element_key2": module.params["element_key2"] or "-", + } + + if proposed not in sections: + if module.params["element_key2"]: + commands.append( + 'snapshot section add {0} "{1}" {2} {3} {4}'.format( + module.params["section"], + module.params["show_command"], + module.params["row_id"], + module.params["element_key1"], + module.params["element_key2"], + ), + ) + else: + commands.append( + 'snapshot section add {0} "{1}" {2} {3}'.format( + module.params["section"], + module.params["show_command"], + module.params["row_id"], + module.params["element_key1"], + ), + ) + + return commands + + +def action_compare(module, existing_snapshots): + command = "show snapshot compare {0} {1}".format( + module.params["snapshot1"], + module.params["snapshot2"], + ) + + if module.params["compare_option"]: + command += " {0}".format(module.params["compare_option"]) + + body = execute_show_command(command, module)[0] + return body + + +def action_delete(module, existing_snapshots): + commands = list() + + exist = False + for snapshot in existing_snapshots: + if module.params["snapshot_name"] == snapshot["name"]: + exist = True + + if exist: + commands.append("snapshot delete {0}".format(module.params["snapshot_name"])) + + return commands + + +def action_delete_all(module, existing_snapshots): + commands = list() + if existing_snapshots: + commands.append("snapshot delete all") + return commands + + +def invoke(name, *args, **kwargs): + func = globals().get(name) + if func: + return func(*args, **kwargs) + + +def write_on_file(content, filename, module): + path = module.params["path"] + if path[-1] != "/": + path += "/" + filepath = "{0}{1}".format(path, filename) + try: + report = open(filepath, "w") + report.write(content) + report.close() + except Exception: + module.fail_json(msg="Error while writing on file.") + + return filepath + + +def main(): + argument_spec = dict( + action=dict( + required=True, + choices=["create", "add", "compare", "delete", "delete_all"], + ), + snapshot_name=dict(type="str"), + description=dict(type="str"), + snapshot1=dict(type="str"), + snapshot2=dict(type="str"), + compare_option=dict(choices=["summary", "ipv4routes", "ipv6routes"]), + comparison_results_file=dict(type="str"), + section=dict(type="str"), + show_command=dict(type="str"), + row_id=dict(type="str"), + element_key1=dict(type="str", no_log=False), + element_key2=dict(type="str", no_log=False), + save_snapshot_locally=dict(type="bool", default=False), + path=dict(type="str", default="./"), + ) + + required_if = [ + ( + "action", + "compare", + ["snapshot1", "snapshot2", "comparison_results_file"], + ), + ("action", "create", ["snapshot_name", "description"]), + ( + "action", + "add", + ["section", "show_command", "row_id", "element_key1"], + ), + ("action", "delete", ["snapshot_name"]), + ] + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True, + ) + + action = module.params["action"] + comparison_results_file = module.params["comparison_results_file"] + + if not os.path.isdir(module.params["path"]): + module.fail_json(msg="{0} is not a valid directory name.".format(module.params["path"])) + + existing_snapshots = invoke("get_existing", module) + action_results = invoke("action_%s" % action, module, existing_snapshots) + + result = {"changed": False, "commands": []} + + if not module.check_mode: + if action == "compare": + result["commands"] = [] + + if module.params["path"] and comparison_results_file: + snapshot1 = module.params["snapshot1"] + snapshot2 = module.params["snapshot2"] + compare_option = module.params["compare_option"] + command = "show snapshot compare {0} {1}".format(snapshot1, snapshot2) + if compare_option: + command += " {0}".format(compare_option) + content = execute_show_command(command, module)[0] + if content: + write_on_file(content, comparison_results_file, module) + else: + if action_results: + load_config(module, action_results) + result["commands"] = action_results + result["changed"] = True + + if ( + action == "create" + and module.params["path"] + and module.params["save_snapshot_locally"] + ): + command = "show snapshot dump {0} | json".format(module.params["snapshot_name"]) + content = execute_show_command(command, module)[0] + if content: + write_on_file(str(content), module.params["snapshot_name"], module) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py new file mode 100644 index 00000000..4e94b1f0 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py @@ -0,0 +1,254 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_snmp_community +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2024-01-01) Manages SNMP community configs. +description: +- Manages SNMP community configuration. +version_added: 1.0.0 +deprecated: + alternative: nxos_snmp_server + why: Updated modules released with more functionality + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +options: + community: + description: + - Case-sensitive community string. + required: true + type: str + access: + description: + - Access type for community. + choices: + - ro + - rw + type: str + group: + description: + - Group to which the community belongs. + type: str + acl: + description: + - ACL name to filter snmp requests or keyword 'default'. + type: str + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# ensure snmp community is configured +- cisco.nxos.nxos_snmp_community: + community: TESTING7 + group: network-operator + state: present +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["snmp-server community TESTING7 group network-operator"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module): + if "show run" not in command: + output = "json" + else: + output = "text" + cmds = [{"command": command, "output": output}] + + body = run_commands(module, cmds) + return body + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_snmp_groups(module): + data = execute_show_command("show snmp group", module)[0] + group_list = [] + + try: + group_table = data["TABLE_role"]["ROW_role"] + for group in group_table: + group_list.append(group["role_name"]) + except (KeyError, AttributeError): + pass + + return group_list + + +def get_snmp_community(module, name): + command = "show run snmp all | grep word-exp {0}".format(name) + data = execute_show_command(command, module)[0] + community_dict = {} + + if not data: + return community_dict + + community_re = r"snmp-server community (\S+)" + mo = re.search(community_re, data) + if mo: + community_name = mo.group(1) + else: + return community_dict + + community_dict["group"] = None + group_re = r"snmp-server community {0} group (\S+)".format(community_name) + mo = re.search(group_re, data) + if mo: + community_dict["group"] = mo.group(1) + + community_dict["acl"] = None + acl_re = r"snmp-server community {0} use-acl (\S+)".format(community_name) + mo = re.search(acl_re, data) + if mo: + community_dict["acl"] = mo.group(1) + + return community_dict + + +def config_snmp_community(delta, community): + CMDS = { + "group": "snmp-server community {0} group {group}", + "acl": "snmp-server community {0} use-acl {acl}", + "no_acl": "no snmp-server community {0} use-acl {no_acl}", + } + commands = [] + for k in delta.keys(): + cmd = CMDS.get(k).format(community, **delta) + if cmd: + if "group" in cmd: + commands.insert(0, cmd) + else: + commands.append(cmd) + cmd = None + return commands + + +def main(): + argument_spec = dict( + community=dict(required=True, type="str"), + access=dict(choices=["ro", "rw"]), + group=dict(type="str"), + acl=dict(type="str"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=[["access", "group"]], + mutually_exclusive=[["access", "group"]], + supports_check_mode=True, + ) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + access = module.params["access"] + group = module.params["group"] + community = module.params["community"] + acl = module.params["acl"] + state = module.params["state"] + + if access: + if access == "ro": + group = "network-operator" + elif access == "rw": + group = "network-admin" + + # group check - ensure group being configured exists on the device + configured_groups = get_snmp_groups(module) + + if group not in configured_groups: + module.fail_json(msg="Group not on switch. Please add before moving forward") + + existing = get_snmp_community(module, community) + args = dict(group=group, acl=acl) + proposed = dict((k, v) for k, v in args.items() if v is not None) + delta = dict(set(proposed.items()).difference(existing.items())) + if delta.get("acl") == "default": + delta.pop("acl") + if existing.get("acl"): + delta["no_acl"] = existing.get("acl") + + commands = [] + + if state == "absent": + if existing: + command = "no snmp-server community {0}".format(community) + commands.append(command) + elif state == "present": + if delta: + command = config_snmp_community(dict(delta), community) + commands.append(command) + + cmds = flatten_list(commands) + + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + + if "configure" in cmds: + cmds.pop(0) + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py new file mode 100644 index 00000000..49dbc8a8 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py @@ -0,0 +1,151 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_snmp_contact +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2024-01-01) Manages SNMP contact info. +description: +- Manages SNMP contact information. +version_added: 1.0.0 +deprecated: + alternative: nxos_snmp_server + why: Updated modules released with more functionality + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +- C(state=absent) removes the contact configuration if it is configured. +options: + contact: + description: + - Contact information. + required: true + type: str + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# ensure snmp contact is configured +- cisco.nxos.nxos_snmp_contact: + contact: Test + state: present +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["snmp-server contact New_Test"] +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module): + command = {"command": command, "output": "text"} + + return run_commands(module, command) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_snmp_contact(module): + contact = {} + contact_regex = r"^\s*snmp-server\scontact\s(?P<contact>.+)$" + + body = execute_show_command("show run snmp", module)[0] + match_contact = re.search(contact_regex, body, re.M) + if match_contact: + contact["contact"] = match_contact.group("contact") + + return contact + + +def main(): + argument_spec = dict( + contact=dict(required=True, type="str"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + contact = module.params["contact"] + state = module.params["state"] + + existing = get_snmp_contact(module) + commands = [] + + if state == "absent": + if existing and existing["contact"] == contact: + commands.append("no snmp-server contact") + elif state == "present": + if not existing or existing["contact"] != contact: + commands.append("snmp-server contact {0}".format(contact)) + + cmds = flatten_list(commands) + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + + if "configure" in cmds: + cmds.pop(0) + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py new file mode 100644 index 00000000..3c23374c --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py @@ -0,0 +1,513 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_snmp_host +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2024-01-01) Manages SNMP host configuration. +description: +- Manages SNMP host configuration parameters. +version_added: 1.0.0 +deprecated: + alternative: nxos_snmp_server + why: Updated modules released with more functionality + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +- C(state=absent) removes the host configuration if it is configured. +options: + snmp_host: + description: + - IP address of hostname of target host. + required: true + type: str + version: + description: + - SNMP version. If this is not specified, v1 is used. + choices: + - v1 + - v2c + - v3 + type: str + v3: + description: + - Use this when verion is v3. SNMPv3 Security level. + choices: + - noauth + - auth + - priv + type: str + community: + description: + - Community string or v3 username. + type: str + udp: + description: + - UDP port number (0-65535). + default: '162' + type: str + snmp_type: + description: + - type of message to send to host. If this is not specified, trap type is used. + choices: + - trap + - inform + type: str + vrf: + description: + - VRF to use to source traffic to source. If state = absent, the vrf is removed. + type: str + vrf_filter: + description: + - Name of VRF to filter. If state = absent, the vrf is removed from the filter. + type: str + src_intf: + description: + - Source interface. Must be fully qualified interface name. If state = absent, + the interface is removed. + type: str + state: + description: + - Manage the state of the resource. If state = present, the host is added to the + configuration. If only vrf and/or vrf_filter and/or src_intf are given, they + will be added to the existing host configuration. If state = absent, the host + is removed if community parameter is given. It is possible to remove only vrf + and/or src_int and/or vrf_filter by providing only those parameters and no community + parameter. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# ensure snmp host is configured +- cisco.nxos.nxos_snmp_host: + snmp_host: 192.0.2.3 + community: TESTING + state: present +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["snmp-server host 192.0.2.3 filter-vrf another_test_vrf"] +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module): + command = {"command": command, "output": "json"} + + return run_commands(module, command) + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if new_key: + value = table.get(key) + if value: + new_dict[new_key] = str(value) + else: + new_dict[new_key] = value + return new_dict + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_snmp_host(host, udp, module): + body = execute_show_command("show snmp host", module) + + host_map = { + "port": "udp", + "version": "version", + "level": "v3", + "type": "snmp_type", + "secname": "community", + } + + host_map_5k = { + "port": "udp", + "version": "version", + "sec_level": "v3", + "notif_type": "snmp_type", + "commun_or_user": "community", + } + + resource = {} + + if body: + try: + resource_table = body[0]["TABLE_host"]["ROW_host"] + + if isinstance(resource_table, dict): + resource_table = [resource_table] + + for each in resource_table: + key = str(each["host"]) + "_" + str(each["port"]).strip() + src = each.get("src_intf") + host_resource = apply_key_map(host_map, each) + + if src: + host_resource["src_intf"] = src + if re.search(r"interface:", src): + host_resource["src_intf"] = src.split(":")[1].strip() + + vrf_filt = each.get("TABLE_vrf_filters") + if vrf_filt: + vrf_filter = vrf_filt["ROW_vrf_filters"]["vrf_filter"].split(":")[1].split(",") + filters = [vrf.strip() for vrf in vrf_filter] + host_resource["vrf_filter"] = filters + + vrf = each.get("vrf") + if vrf: + host_resource["vrf"] = vrf.split(":")[1].strip() + resource[key] = host_resource + except KeyError: + # Handle the 5K case + try: + resource_table = body[0]["TABLE_hosts"]["ROW_hosts"] + + if isinstance(resource_table, dict): + resource_table = [resource_table] + + for each in resource_table: + key = str(each["address"]) + "_" + str(each["port"]).strip() + src = each.get("src_intf") + host_resource = apply_key_map(host_map_5k, each) + + if src: + host_resource["src_intf"] = src + if re.search(r"interface:", src): + host_resource["src_intf"] = src.split(":")[1].strip() + + vrf = each.get("use_vrf_name") + if vrf: + host_resource["vrf"] = vrf.strip() + + vrf_filt = each.get("TABLE_filter_vrf") + if vrf_filt: + vrf_filter = vrf_filt["ROW_filter_vrf"]["filter_vrf_name"].split(",") + filters = [vrf.strip() for vrf in vrf_filter] + host_resource["vrf_filter"] = filters + + resource[key] = host_resource + except (KeyError, AttributeError, TypeError): + return resource + except (AttributeError, TypeError): + return resource + + find = resource.get(host + "_" + udp) + + if find: + fix_find = {} + for key, value in find.items(): + if isinstance(value, str): + fix_find[key] = value.strip() + else: + fix_find[key] = value + return fix_find + + return {} + + +def remove_snmp_host(host, udp, existing): + commands = [] + if existing["version"] == "v3": + existing["version"] = "3" + command = "no snmp-server host {0} {snmp_type} version \ + {version} {v3} {community} udp-port {1}".format( + host, udp, **existing + ) + + elif existing["version"] == "v2c": + existing["version"] = "2c" + command = "no snmp-server host {0} {snmp_type} version \ + {version} {community} udp-port {1}".format( + host, udp, **existing + ) + + elif existing["version"] == "v1": + existing["version"] = "1" + command = "no snmp-server host {0} {snmp_type} version \ + {version} {community} udp-port {1}".format( + host, udp, **existing + ) + + if command: + commands.append(command) + return commands + + +def remove_vrf(host, udp, proposed, existing): + commands = [] + if existing.get("vrf"): + commands.append( + "no snmp-server host {0} use-vrf \ + {1} udp-port {2}".format( + host, + proposed.get("vrf"), + udp, + ), + ) + return commands + + +def remove_filter(host, udp, proposed, existing): + commands = [] + if existing.get("vrf_filter"): + if proposed.get("vrf_filter") in existing.get("vrf_filter"): + commands.append( + "no snmp-server host {0} filter-vrf \ + {1} udp-port {2}".format( + host, + proposed.get("vrf_filter"), + udp, + ), + ) + return commands + + +def remove_src(host, udp, proposed, existing): + commands = [] + if existing.get("src_intf"): + commands.append( + "no snmp-server host {0} source-interface \ + {1} udp-port {2}".format( + host, + proposed.get("src_intf"), + udp, + ), + ) + return commands + + +def config_snmp_host(delta, udp, proposed, existing, module): + commands = [] + command_builder = [] + host = proposed["snmp_host"] + cmd = "snmp-server host {0}".format(proposed["snmp_host"]) + + snmp_type = delta.get("snmp_type") + version = delta.get("version") + ver = delta.get("v3") + community = delta.get("community") + + command_builder.append(cmd) + if any([snmp_type, version, ver, community]): + type_string = snmp_type or existing.get("type") + if type_string: + command_builder.append(type_string) + + version = version or existing.get("version") + if version: + if version == "v1": + vn = "1" + elif version == "v2c": + vn = "2c" + elif version == "v3": + vn = "3" + + version_string = "version {0}".format(vn) + command_builder.append(version_string) + + if ver: + ver_string = ver or existing.get("v3") + command_builder.append(ver_string) + + if community: + community_string = community or existing.get("community") + command_builder.append(community_string) + + udp_string = " udp-port {0}".format(udp) + command_builder.append(udp_string) + + cmd = " ".join(command_builder) + + commands.append(cmd) + + CMDS = { + "vrf_filter": "snmp-server host {0} filter-vrf {vrf_filter} udp-port {1}", + "vrf": "snmp-server host {0} use-vrf {vrf} udp-port {1}", + "src_intf": "snmp-server host {0} source-interface {src_intf} udp-port {1}", + } + + for key in delta: + command = CMDS.get(key) + if command: + cmd = command.format(host, udp, **delta) + commands.append(cmd) + return commands + + +def main(): + argument_spec = dict( + snmp_host=dict(required=True, type="str"), + community=dict(type="str"), + udp=dict(type="str", default="162"), + version=dict(choices=["v1", "v2c", "v3"]), + src_intf=dict(type="str"), + v3=dict(choices=["noauth", "auth", "priv"]), + vrf_filter=dict(type="str"), + vrf=dict(type="str"), + snmp_type=dict(choices=["trap", "inform"]), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + snmp_host = module.params["snmp_host"] + community = module.params["community"] + udp = module.params["udp"] + version = module.params["version"] + src_intf = module.params["src_intf"] + v3 = module.params["v3"] + vrf_filter = module.params["vrf_filter"] + vrf = module.params["vrf"] + snmp_type = module.params["snmp_type"] + state = module.params["state"] + + existing = get_snmp_host(snmp_host, udp, module) + + if version is None: + if existing: + version = existing.get("version") + else: + version = "v1" + + if snmp_type is None: + if existing: + snmp_type = existing.get("snmp_type") + else: + snmp_type = "trap" + + if v3 is None: + if version == "v3" and existing: + v3 = existing.get("v3") + + if snmp_type == "inform" and version == "v1": + module.fail_json(msg="inform requires snmp v2c or v3") + + if (version == "v1" or version == "v2c") and v3: + module.fail_json(msg='param: "v3" should not be used when ' "using version v1 or v2c") + + if not any([vrf_filter, vrf, src_intf]): + if not all([snmp_type, version, community, udp]): + module.fail_json( + msg="when not configuring options like " + "vrf_filter, vrf, and src_intf," + "the following params are required: " + "type, version, community", + ) + + if version == "v3" and v3 is None: + module.fail_json( + msg="when using version=v3, the param v3 " + "(options: auth, noauth, priv) is also required", + ) + + # existing returns the list of vrfs configured for a given host + # checking to see if the proposed is in the list + store = existing.get("vrf_filter") + if existing and store: + if vrf_filter not in existing["vrf_filter"]: + existing["vrf_filter"] = None + else: + existing["vrf_filter"] = vrf_filter + commands = [] + + args = dict( + community=community, + snmp_host=snmp_host, + udp=udp, + version=version, + src_intf=src_intf, + vrf_filter=vrf_filter, + v3=v3, + vrf=vrf, + snmp_type=snmp_type, + ) + proposed = dict((k, v) for k, v in args.items() if v is not None) + + if state == "absent" and existing: + if proposed.get("community"): + commands.append(remove_snmp_host(snmp_host, udp, existing)) + else: + if proposed.get("src_intf"): + commands.append(remove_src(snmp_host, udp, proposed, existing)) + if proposed.get("vrf"): + commands.append(remove_vrf(snmp_host, udp, proposed, existing)) + if proposed.get("vrf_filter"): + commands.append(remove_filter(snmp_host, udp, proposed, existing)) + + elif state == "present": + delta = dict(set(proposed.items()).difference(existing.items())) + if delta: + command = config_snmp_host(delta, udp, proposed, existing, module) + commands.append(command) + + cmds = flatten_list(commands) + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + + if "configure" in cmds: + cmds.pop(0) + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py new file mode 100644 index 00000000..d7526a9e --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_snmp_location +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2024-01-01) Manages SNMP location information. +description: +- Manages SNMP location configuration. +version_added: 1.0.0 +deprecated: + alternative: nxos_snmp_server + why: Updated modules released with more functionality + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +options: + location: + description: + - Location information. + required: true + type: str + state: + description: + - Manage the state of the resource. + required: false + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# ensure snmp location is configured +- cisco.nxos.nxos_snmp_location: + location: Test + state: present + +# ensure snmp location is not configured +- cisco.nxos.nxos_snmp_location: + location: Test + state: absent +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["snmp-server location New_Test"] +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module): + command = {"command": command, "output": "text"} + + return run_commands(module, command) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_snmp_location(module): + location = {} + location_regex = r"^\s*snmp-server\s+location\s+(?P<location>.+)$" + + body = execute_show_command("show run snmp", module)[0] + match_location = re.search(location_regex, body, re.M) + if match_location: + location["location"] = match_location.group("location") + + return location + + +def main(): + argument_spec = dict( + location=dict(required=True, type="str"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + location = module.params["location"] + state = module.params["state"] + + existing = get_snmp_location(module) + commands = [] + + if state == "absent": + if existing and existing["location"] == location: + commands.append("no snmp-server location") + elif state == "present": + if not existing or existing["location"] != location: + commands.append("snmp-server location {0}".format(location)) + + cmds = flatten_list(commands) + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + + if "configure" in cmds: + cmds.pop(0) + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py new file mode 100644 index 00000000..7354dc1e --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py @@ -0,0 +1,1480 @@ +#!/usr/bin/python +# -*- 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 module file for nxos_snmp_server +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: nxos_snmp_server +short_description: SNMP Server resource module. +description: +- This module manages SNMP server configuration on devices running Cisco NX-OS. +version_added: 2.8.0 +notes: +- Tested against NX-OS 9.3.6 on Cisco Nexus Switches. +- This module works with connection C(network_cli) and C(httpapi). +- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli). +author: Nilashish Chakraborty (@NilashishC) +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the command B(show running-config | section '^snmp-server'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dict of SNMP server configuration + type: dict + suboptions: + aaa_user: + description: Set duration for which aaa-cached snmp user exists. + type: dict + suboptions: + cache_timeout: + description: Timeout for which aaa-cached user exists(in secs). + type: int + communities: + description: Set community string and access privs. + type: list + elements: dict + suboptions: + name: + description: SNMP community string (Max Size 32). + type: str + aliases: ["community"] + group: + description: Group to which the community belongs. + type: str + ro: + description: Read-only access with this community string. + type: bool + rw: + description: Read-write access with this community string. + type: bool + use_ipv4acl: + description: + - Specify IPv4 ACL, the ACL name specified must be IPv4 ACL. + - This option is unsupported on MDS switches. + type: str + use_ipv6acl: + description: + - Specify IPv6 ACL, the ACL name specified after must be IPv6 ACL. + - This option is unsupported on MDS switches. + type: str + contact: + description: Modify sysContact. + type: str + context: + description: SNMP context to be mapped. + type: dict + suboptions: + name: + description: Name of the SNMP context (Max Size 32). + type: str + instance: + description: Name of the protocol instance (Max Size 32). + type: str + topology: + description: Topology associated with the SNMP context. + type: str + vrf: + description: + - VRF associated with the SNMP context. + - This option is unsupported on MDS switches. + type: str + counter: + description: + - Configure port counter configuration. + - This option is unsupported on MDS switches. + type: dict + suboptions: + cache: + description: Port stats cache. + type: dict + suboptions: + enable: + description: Enable port stats cache. + type: bool + timeout: + description: Timeout for which cached port stats exists(in secs). + type: int + drop: + description: + - Silently drop unknown v3 user packets. + - This option is unsupported on MDS switches. + type: dict + suboptions: + unknown_engine_id: + description: Unknown v3 engine id. + type: bool + unknown_user: + description: Unknown v3 user. + type: bool + traps: + description: Enable SNMP Traps + type: dict + suboptions: + aaa: + description: AAA traps + type: dict + suboptions: + enable: + description: Enable AAA traps. + type: bool + server_state_change: + description: AAA server state change notification. + type: bool + bgp: + description: SNMP BGP traps. + type: dict + suboptions: + enable: + description: Enable SNMP BGP traps. + type: bool + bridge: + description: + - Bridge traps. + - This option is unsupported on MDS switches. + type: dict + suboptions: + enable: + description: Enable bridge traps. + type: bool + newroot: + description: Enable SNMP STP Bridge MIB newroot traps. + type: bool + topologychange: + description: Enable SNMP STP Bridge MIB topologychange traps. + type: bool + callhome: + description: Callhome traps. + type: dict + suboptions: + enable: + description: + - Enable callhome traps. + - This option is unsupported on MDS switches. + type: bool + event_notify: + description: Callhome External Event Notification. + type: bool + smtp_send_fail: + description: SMTP Message Send Fail notification. + type: bool + cfs: + description: CFS traps. + type: dict + suboptions: + enable: + description: + - Enable cfs traps. + - This option is unsupported on MDS switches. + type: bool + merge_failure: + description: Merge failure notification. + type: bool + state_change_notif: + description: State change notification. + type: bool + config: + description: Config traps. + type: dict + suboptions: + enable: + description: + - Enable config traps. + - This option is unsupported on MDS switches. + type: bool + ccmCLIRunningConfigChanged: + description: Running config change trap. + type: bool + entity: + description: Entity traps. + type: dict + suboptions: + enable: + description: Enable entity traps. + type: bool + cefcMIBEnableStatusNotification: + description: CefcMIBEnableStatusNotification. + type: bool + entity_fan_status_change: + description: Entity Fan Status Change. + type: bool + entity_mib_change: + description: Entity MIB change. + type: bool + entity_module_inserted: + description: Entity Module Inserted. + type: bool + entity_module_removed: + description: Entity Module Removed. + type: bool + entity_module_status_change: + description: Entity Module Status Change. + type: bool + entity_power_out_change: + description: Entity Power Out Change. + type: bool + entity_power_status_change: + description: Entity Power Status Change. + type: bool + entity_sensor: + description: Entity sensor. + type: bool + entity_unrecognised_module: + description: Entity Unrecognised Module. + type: bool + feature_control: + description: Feature-Control traps. + type: dict + suboptions: + enable: + description: + - Enable feature-control traps. + - This option is unsupported on MDS switches. + type: bool + featureOpStatusChange: + description: Feature operation status change notification. + type: bool + ciscoFeatOpStatusChange: + description: Feature operation status change Notification. + type: bool + generic: + description: Generic traps. + type: dict + suboptions: + enable: + description: + - Enable generic traps. + - This option is unsupported on MDS switches. + type: bool + coldStart: + description: Generic coldStart trap. + type: bool + warmStart: + description: Generic warmStart trap. + type: bool + license: + description: License traps. + type: dict + suboptions: + enable: + description: + - Enable license traps. + - This option is unsupported on MDS switches. + type: bool + notify_license_expiry: + description: License Expiry Notification. + type: bool + notify_license_expiry_warning: + description: License Expiry Warning Notification. + type: bool + notify_licensefile_missing: + description: License File Missing Notification. + type: bool + notify_no_license_for_feature: + description: No License installed for feature Notification. + type: bool + link: + description: Link traps. + type: dict + suboptions: + enable: + description: + - Enable link traps. + - This option is unsupported on MDS switches. + type: bool + cErrDisableInterfaceEventRev1: + description: + - Err-disable state notification. + - This option is unsupported on MDS switches. + type: bool + cieLinkDown: + description: Cisco extended link state down notification. + type: bool + cieLinkUp: + description: Cisco extended link state up notification. + type: bool + cisco_xcvr_mon_status_chg: + description: Cisco interface transceiver monitor status change notification. + type: bool + cmn_mac_move_notification: + description: + - Mac addr move trap. + - This option is unsupported on MDS switches. + type: bool + delayed_link_state_change: + description: Delayed link state change. + type: bool + extended_linkDown: + description: IETF extended link state down notification. + type: bool + extended_linkUp: + description: IETF extended link state up notification. + type: bool + linkDown: + description: IETF Link state down notification. + type: bool + linkUp: + description: IETF Link state up notification. + type: bool + mmode: + description: + - MMode traps. + - This option is unsupported on MDS switches. + type: dict + suboptions: + enable: + description: Enable mmode traps. + type: bool + cseMaintModeChangeNotify: + description: Maint Mode Change Notification. + type: bool + cseNormalModeChangeNotify: + description: Normal Mode Change Notification. + type: bool + ospf: + description: SNMP OSPF traps. + type: dict + suboptions: + enable: + description: Enable SNMP OSPF traps. + type: bool + ospfv3: + description: SNMP OSPFv3 traps. + type: dict + suboptions: + enable: + description: Enable SNMP OSPFv3 traps. + type: bool + rf: + description: RF traps. + type: dict + suboptions: + enable: + description: + - Enable rf traps. + - This option is unsupported on MDS switches. + type: bool + redundancy_framework: + description: Redundancy_Framework (RF) Sup switchover MIB. + type: bool + rmon: + description: RMON traps. + type: dict + suboptions: + enable: + description: + - Enable rmon traps. + - This option is unsupported on MDS switches. + type: bool + fallingAlarm: + description: Rmon falling alarm. + type: bool + hcFallingAlarm: + description: High capacity Rmon falling alarm. + type: bool + hcRisingAlarm: + description: High capacity Rmon rising alarm. + type: bool + risingAlarm: + description: Rmon rising alarm. + type: bool + snmp: + description: SNMP traps. + type: dict + suboptions: + enable: + description: + - Enable snmp traps. + - This option is unsupported on MDS switches. + type: bool + authentication: + description: SNMP authentication trap. + type: bool + storm_control: + description: Storm-Control traps. + type: dict + suboptions: + enable: + description: + - Enable storm-control traps. + - This option is unsupported on MDS switches. + type: bool + cpscEventRev1: + description: + - Port-Storm-Control-Event. + - This option is unsupported on MDS switches. + type: bool + trap_rate: + description: Number of traps per minute. + type: bool + stpx: + description: + - Stpx traps. + - This option is unsupported on MDS switches. + type: dict + suboptions: + enable: + description: Enable stpx traps. + type: bool + inconsistency: + description: Enable SNMP STPX MIB InconsistencyUpdate traps. + type: bool + loop_inconsistency: + description: Enable SNMP STPX MIB LoopInconsistencyUpdate traps. + type: bool + root_inconsistency: + description: Enable SNMP STPX MIB RootInconsistencyUpdate traps. + type: bool + syslog: + description: Enable syslog traps. + type: dict + suboptions: + enable: + description: + - Enable syslog traps. + - This option is unsupported on MDS switches. + type: bool + message_generated: + description: Message Generated Notification. + type: bool + sysmgr: + description: Sysmgr traps. + type: dict + suboptions: + enable: + description: + - Enable sysmgr traps. + - This option is unsupported on MDS switches. + type: bool + cseFailSwCoreNotifyExtended: + description: Software Core Notification. + type: bool + system: + description: System traps. + type: dict + suboptions: + enable: + description: + - Enable system traps. + - This option is unsupported on MDS switches. + type: bool + clock_change_notification: + description: Clock-change-notification traps. + type: bool + upgrade: + description: Upgrade traps. + type: dict + suboptions: + enable: + description: + - Enable upgrade traps. + - This option is unsupported on MDS switches. + type: bool + upgradeJobStatusNotify: + description: Upgrade Job Status Notification. + type: bool + upgradeOpNotifyOnCompletion: + description: Upgrade Global Status Notification. + type: bool + vtp: + description: + - VTP traps. + - This option is unsupported on MDS switches. + type: dict + suboptions: + enable: + description: Enable VTP traps. + type: bool + notifs: + description: + - Enable vtpConfigRevNumberError vtpConfigDigestEnable vtpConfigRevNumberError vtpConfigDigestError + vtpServerDisabled vtpVersionOneDeviceDetected vlanTrunkPortDynamicStatusChange vtpLocalModeChanged + vtpVersionInUseChanged notification. + type: bool + vlancreate: + description: Enable vtpVlanCreated notification. + type: bool + vlandelete: + description: Enable vtpVlanDeleted notification. + type: bool + engine_id: + description: + - Configure a local SNMPv3 engineID. + - This option is unsupported on MDS switches. + type: dict + suboptions: + local: + description: EngineID of the local agent. + type: str + global_enforce_priv: + description: Globally enforce privacy for all the users. + type: bool + hosts: + description: + - Specify hosts to receive SNMP notifications. + - SNMP hosts config lines that appear separately in running-config must be added as individual dictionaries. + type: list + elements: dict + suboptions: + host: + description: IPv4 or IPv6 address or DNS Name of SNMP notification host. + type: str + community: + description: SNMP community string or SNMPv3 user name (Max Size 32). + type: str + filter_vrf: + description: + - Filters notifications to the notification host receiver based on the configured VRF. + - This option is unsupported on MDS switches. + type: str + informs: + description: Send Inform messages to this host. + type: bool + source_interface: + description: Source interface to be used for sending out SNMP notifications to this host. + type: str + traps: + description: Send Traps messages to this host. + type: bool + use_vrf: + description: + - Configures SNMP to use the selected VRF to communicate with the host receiver. + - This option is unsupported on MDS switches. + type: str + version: + description: SNMP version to use for notification messages. + type: str + choices: ["1", "2c", "3"] + auth: + description: Use the SNMPv3 authNoPriv Security Level. + type: str + priv: + description: Use the SNMPv3 authPriv Security Level. + type: str + udp_port: + description: The notification host's UDP port number. + type: int + location: + description: Modify sysLocation. + type: str + mib: + description: Mib access parameters. + type: dict + suboptions: + community_map: + description: SNMP community. + type: dict + suboptions: + community: + description: SNMP community string (Max Size 32). + type: str + context: + description: Name of the SNMP context (Max Size 32). + type: str + packetsize: + description: Largest SNMP packet size + type: int + protocol: + description: Snmp protocol operations. + type: dict + suboptions: + enable: + description: Enable/Disable snmp protocol operations. + type: bool + source_interface: + description: + - Source interface to be used for sending out SNMP notifications. + - This option is unsupported on MDS switches. + type: dict + suboptions: + informs: + description: SNMP Inform notifications for which this source interface needs to be used. + type: str + traps: + description: SNMP Trap notifications for which this source interface needs to be used. + type: str + system_shutdown: + description: Configure snmp-server for reload(2). + type: bool + tcp_session: + description: Enable one time authentication for snmp over tcp session. + type: dict + suboptions: + enable: + description: + - Enable tcp-session. + - This option is unsupported on MDS switches. + type: bool + auth: + description: Enable one time authentication for snmp over tcp session. + type: bool + users: + description: Define users who can access the SNMP engine. + type: dict + suboptions: + auth: + description: SNMP User authentication related settings + type: list + elements: dict + suboptions: + user: + description: Name of the user (Max Size 28). + type: str + group: + description: Group name (ignored for notif target user) (Max Size 28). + type: str + authentication: + description: Authentication parameters for the user. + type: dict + suboptions: + algorithm: + description: Select algorithm for authentication. + type: str + choices: ["md5", "sha", "sha-256"] + password: + description: + - Authentication password for user (Max Size 127). + - If this value is localized, it has to be enclosed in quotes in the task. + type: str + engine_id: + description: + - EngineID for configuring notif target user (for V3 informs). + - This value needs to be enclosed in quotes in the task. + type: str + localized_key: + description: Specifies whether the passwords are in localized key format. + type: bool + localizedv2_key: + description: Specifies whether the passwords are in localized V2 key format. + type: bool + priv: + description: Encryption parameters for the user. + type: dict + suboptions: + privacy_password: + description: + - Privacy password for user (Max Size 130). + - If this value is localized, it has to be enclosed in quotes in the task. + type: str + aes_128: + description: Use 128-bit AES algorithm for privacy. + type: bool + use_acls: + description: Set IPv4 and IPv6 ACL to use. + type: list + elements: dict + suboptions: + user: + description: Name of the user (Max Size 28). + type: str + ipv4: + description: Specify IPv4 ACL, the ACL name specified after must be IPv4 ACL. + type: str + ipv6: + description: Specify IPv6 ACL, the ACL name specified after must be IPv6 ACL. + type: str + state: + description: + - The state the configuration should be left in. + - The states C(replaced) and C(overridden) have identical behaviour for this module. + - Please refer to examples for more details. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - parsed + - gathered + - rendered + default: merged +""" + +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# nxos-9k-rdo# show running-config | section "^snmp-server" +# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey + +- name: Merge the provided configuration with the existing running configuration + cisco.nxos.nxos_snmp_server: + config: + aaa_user: + cache_timeout: 36000 + communities: + - community: public + group: network-operator + - community: private + group: network-admin + contact: nxosswitchadmin@localhost + location: serverroom-1 + traps: + aaa: + server_state_change: True + system: + clock_change_notification: True + hosts: + - host: 192.0.2.1 + traps: True + version: '1' + community: public + - host: 192.0.2.1 + source_interface: Ethernet1/1 + - host: 192.0.2.2 + informs: True + version: '3' + auth: NMS + users: + auth: + - user: snmp_user_1 + group: network-operator + authentication: + algorithm: md5 + password: '0x5632724fb8ac3699296af26281e1d0f1' + localized_key: True + - user: snmp_user_2 + group: network-operator + authentication: + algorithm: md5 + password: '0x5632724fb8ac3699296af26281e1d0f1' + localized_key: True + priv: + privacy_password: '0x5632724fb8ac3699296af26281e1d0f1' + aes_128: True + use_acls: + - user: snmp_user_1 + ipv4: acl1 + ipv6: acl2 + - user: snmp_user_2 + ipv4: acl3 + ipv6: acl4 + +# Task output +# ------------- +# before: +# users: +# auth: +# - user: admin +# group: network-admin +# authentication: +# algorithm: md5 +# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# localized_key: True +# priv: +# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# +# commands: +# - snmp-server contact nxosswitchadmin@localhost +# - snmp-server location serverroom-1 +# - snmp-server aaa-user cache-timeout 36000 +# - snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# - snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# - snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2 +# - snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# - snmp-server host 192.0.2.1 traps version 1 public +# - snmp-server host 192.0.2.1 source-interface Ethernet1/1 +# - snmp-server host 192.0.2.2 informs version 3 auth NMS +# - snmp-server community private group network-admin +# - snmp-server community public group network-operator +# - snmp-server enable traps aaa server-state-change +# - snmp-server enable traps system Clock-change-notification +# +# after: +# aaa_user: +# cache_timeout: 36000 +# communities: +# - community: private +# group: network-admin +# - community: public +# group: network-operator +# contact: nxosswitchadmin@localhost +# location: serverroom-1 +# traps: +# aaa: +# server_state_change: True +# system: +# clock_change_notification: True +# hosts: +# - host: 192.0.2.1 +# traps: true +# version: "1" +# community: public +# +# - host: 192.0.2.1 +# source_interface: Ethernet1/1 +# +# - host: 192.0.2.2 +# informs: true +# version: "3" +# auth: NMS +# users: +# auth: +# - user: admin +# group: network-admin +# authentication: +# algorithm: md5 +# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# localized_key: True +# priv: +# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# +# - user: snmp_user_1 +# group: network-operator +# authentication: +# algorithm: md5 +# password: "0x5632724fb8ac3699296af26281e1d0f1" +# localized_key: True +# +# - authentication: +# algorithm: md5 +# localized_key: true +# password: "0x5632724fb8ac3699296af26281e1d0f1" +# priv: +# aes_128: true +# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1" +# group: network-operator +# user: snmp_user_2 +# +# use_acls: +# - user: snmp_user_1 +# ipv4: acl1 +# ipv6: acl2 +# - user: snmp_user_2 +# ipv4: acl3 +# ipv6: acl4 + +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section "^snmp-server" +# snmp-server contact nxosswitchadmin@localhost +# snmp-server location serverroom-1 +# snmp-server aaa-user cache-timeout 36000 +# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey +# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2 +# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# snmp-server host 192.0.2.1 traps version 1 public +# snmp-server host 192.0.2.1 source-interface Ethernet1/1 +# snmp-server host 192.0.2.2 informs version 3 auth NMS +# snmp-server community private group network-admin +# snmp-server community public group network-operator +# snmp-server enable traps aaa server-state-change +# snmp-server enable traps system Clock-change-notification + +# Using replaced + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section "^snmp-server" +# snmp-server contact nxosswitchadmin@localhost +# snmp-server location serverroom-1 +# snmp-server aaa-user cache-timeout 36000 +# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey +# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2 +# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# snmp-server host 192.0.2.1 traps version 1 public +# snmp-server host 192.0.2.1 source-interface Ethernet1/1 +# snmp-server host 192.0.2.2 informs version 3 auth NMS +# snmp-server community private group network-admin +# snmp-server community public group network-operator +# snmp-server enable traps aaa server-state-change +# snmp-server enable traps system Clock-change-notification + +- name: Replace snmp-server configurations of listed snmp-server with provided configurations + cisco.nxos.nxos_snmp_server: + config: + aaa_user: + cache_timeout: 36000 + communities: + - community: public + group: network-operator + - community: secret + group: network-operator + contact: nxosswitchadmin2@localhost + location: serverroom-2 + traps: + aaa: + server_state_change: True + hosts: + - host: 192.0.2.1 + traps: True + version: '1' + community: public + - host: 192.0.2.1 + source_interface: Ethernet1/1 + - host: 192.0.3.2 + informs: True + version: '3' + auth: NMS + users: + auth: + - user: admin + group: network-admin + authentication: + algorithm: md5 + password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" + localized_key: True + priv: + privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" + + - user: snmp_user_1 + group: network-operator + authentication: + algorithm: md5 + password: '0x5632724fb8ac3699296af26281e1d0f1' + localized_key: True + + - user: snmp_user_2 + group: network-operator + authentication: + algorithm: md5 + password: '0x5632724fb8ac3699296af26281e1d0f1' + localized_key: True + priv: + privacy_password: '0x5632724fb8ac3699296af26281e1d0f1' + aes_128: True + use_acls: + - user: snmp_user_1 + ipv4: acl1 + ipv6: acl2 + state: replaced + +# Task output +# ------------- +# before: +# aaa_user: +# cache_timeout: 36000 +# communities: +# - community: private +# group: network-admin +# - community: public +# group: network-operator +# contact: nxosswitchadmin@localhost +# location: serverroom-1 +# traps: +# aaa: +# server_state_change: True +# system: +# clock_change_notification: True +# hosts: +# - host: 192.0.2.1 +# traps: true +# version: "1" +# community: public +# +# - host: 192.0.2.1 +# source_interface: Ethernet1/1 +# +# - host: 192.0.2.2 +# informs: true +# version: "3" +# auth: NMS +# users: +# auth: +# - user: admin +# group: network-admin +# authentication: +# algorithm: md5 +# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# localized_key: True +# priv: +# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# +# - user: snmp_user_1 +# group: network-operator +# authentication: +# algorithm: md5 +# password: "0x5632724fb8ac3699296af26281e1d0f1" +# localized_key: True +# +# - authentication: +# algorithm: md5 +# localized_key: true +# password: "0x5632724fb8ac3699296af26281e1d0f1" +# priv: +# aes_128: true +# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1" +# group: network-operator +# user: snmp_user_2 +# +# use_acls: +# - user: snmp_user_1 +# ipv4: acl1 +# ipv6: acl2 +# - user: snmp_user_2 +# ipv4: acl3 +# ipv6: acl4 +# +# commands: +# - snmp-server contact nxosswitchadmin2@localhost +# - no snmp-server enable traps system Clock-change-notification +# - snmp-server location serverroom-2 +# - no snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# - no snmp-server host 192.0.2.2 informs version 3 auth NMS +# - snmp-server host 192.0.3.2 informs version 3 auth NMS +# - no snmp-server community private group network-admin +# - snmp-server community secret group network-operator +# +# after: +# aaa_user: +# cache_timeout: 36000 +# communities: +# - community: public +# group: network-operator +# - community: secret +# group: network-operator +# contact: nxosswitchadmin2@localhost +# location: serverroom-2 +# traps: +# aaa: +# server_state_change: True +# hosts: +# - host: 192.0.2.1 +# traps: True +# version: '1' +# community: public +# - host: 192.0.2.1 +# source_interface: Ethernet1/1 +# - host: 192.0.3.2 +# informs: True +# version: '3' +# auth: NMS +# users: +# auth: +# - user: admin +# group: network-admin +# authentication: +# algorithm: md5 +# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# localized_key: True +# priv: +# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# +# - user: snmp_user_1 +# group: network-operator +# authentication: +# algorithm: md5 +# password: '0x5632724fb8ac3699296af26281e1d0f1' +# localized_key: True +# +# - user: snmp_user_2 +# group: network-operator +# authentication: +# algorithm: md5 +# password: '0x5632724fb8ac3699296af26281e1d0f1' +# localized_key: True +# priv: +# privacy_password: '0x5632724fb8ac3699296af26281e1d0f1' +# aes_128: True +# +# use_acls: +# - user: snmp_user_1 +# ipv4: acl1 +# ipv6: acl2 +# + +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section "^snmp-server" +# snmp-server contact nxosswitchadmin2@localhost +# snmp-server location serverroom-2 +# snmp-server aaa-user cache-timeout 36000 +# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey +# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2 +# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# snmp-server host 192.0.2.1 traps version 1 public +# snmp-server host 192.0.2.1 source-interface Ethernet1/1 +# snmp-server host 192.0.2.2 informs version 3 auth NMS +# snmp-server community secret group network-operator +# snmp-server community public group network-operator +# snmp-server enable traps aaa server-state-change +# snmp-server enable traps system Clock-change-notification + +# Using deleted + +# Before state: +# ------------ +# nxos-9k-rdo# show running-config | section "^snmp-server" +# snmp-server contact nxosswitchadmin@localhost +# snmp-server location serverroom-1 +# snmp-server aaa-user cache-timeout 36000 +# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey +# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2 +# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# snmp-server host 192.0.2.1 traps version 1 public +# snmp-server host 192.0.2.1 source-interface Ethernet1/1 +# snmp-server host 192.0.2.2 informs version 3 auth NMS +# snmp-server community private group network-admin +# snmp-server community public group network-operator +# snmp-server enable traps aaa server-state-change +# snmp-server enable traps system Clock-change-notification + +- name: Delete SNMP Server configurations from the device (admin user will not be deleted) + cisco.nxos.nxos_snmp_server: + state: deleted + +# Task output +# ------------- +# before: +# aaa_user: +# cache_timeout: 36000 +# communities: +# - community: private +# group: network-admin +# - community: public +# group: network-operator +# contact: nxosswitchadmin@localhost +# location: serverroom-1 +# traps: +# aaa: +# server_state_change: True +# system: +# clock_change_notification: True +# hosts: +# - host: 192.0.2.1 +# traps: true +# version: "1" +# community: public +# +# - host: 192.0.2.1 +# source_interface: Ethernet1/1 +# +# - host: 192.0.2.2 +# informs: true +# version: "3" +# auth: NMS +# users: +# auth: +# - user: admin +# group: network-admin +# authentication: +# algorithm: md5 +# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# localized_key: True +# priv: +# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# +# - user: snmp_user_1 +# group: network-operator +# authentication: +# algorithm: md5 +# password: "0x5632724fb8ac3699296af26281e1d0f1" +# localized_key: True +# +# - authentication: +# algorithm: md5 +# localized_key: true +# password: "0x5632724fb8ac3699296af26281e1d0f1" +# priv: +# aes_128: true +# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1" +# group: network-operator +# user: snmp_user_2 +# +# use_acls: +# - user: snmp_user_1 +# ipv4: acl1 +# ipv6: acl2 +# - user: snmp_user_2 +# ipv4: acl3 +# ipv6: acl4 +# +# commands: +# - no snmp-server contact nxosswitchadmin@localhost +# - no snmp-server location serverroom-1 +# - no snmp-server aaa-user cache-timeout 36000 +# - no snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey +# - no snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# - no snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# - no snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2 +# - no snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# - no snmp-server host 192.0.2.1 traps version 1 public +# - no snmp-server host 192.0.2.1 source-interface Ethernet1/1 +# - no snmp-server host 192.0.2.2 informs version 3 auth NMS +# - no snmp-server community private group network-admin +# - no snmp-server community public group network-operator +# - no snmp-server enable traps aaa server-state-change +# - no snmp-server enable traps system Clock-change-notification +# +# after: +# users: +# auth: +# - user: admin +# group: network-admin +# authentication: +# algorithm: md5 +# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" +# localized_key: True +# priv: +# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9" + +# After state: +# ------------ +# nxos-9k-rdo# show running-config | section "^snmp-server" +# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey + +# Using rendered +# --------------- + +- name: Render platform specific configuration lines with state rendered (without connecting to the device) + cisco.nxos.nxos_snmp_server: + config: + aaa_user: + cache_timeout: 36000 + communities: + - community: public + group: network-operator + - community: private + group: network-admin + contact: nxosswitchadmin@localhost + location: serverroom-1 + traps: + aaa: + server_state_change: True + system: + clock_change_notification: True + hosts: + - host: 192.0.2.1 + traps: True + version: '1' + community: public + - host: 192.0.2.1 + source_interface: Ethernet1/1 + - host: 192.0.2.2 + informs: True + version: '3' + auth: NMS + users: + auth: + - user: snmp_user_1 + group: network-operator + authentication: + algorithm: md5 + password: '0x5632724fb8ac3699296af26281e1d0f1' + localized_key: True + - user: snmp_user_2 + group: network-operator + authentication: + algorithm: md5 + password: '0x5632724fb8ac3699296af26281e1d0f1' + localized_key: True + priv: + privacy_password: '0x5632724fb8ac3699296af26281e1d0f1' + aes_128: True + use_acls: + - user: snmp_user_1 + ipv4: acl1 + ipv6: acl2 + - user: snmp_user_2 + ipv4: acl3 + ipv6: acl4 + state: rendered + + +# Task Output (redacted) +# ----------------------- +# rendered: +# - snmp-server contact nxosswitchadmin@localhost +# - snmp-server location serverroom-1 +# - snmp-server aaa-user cache-timeout 36000 +# - snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# - snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# - snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2 +# - snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# - snmp-server host 192.0.2.1 traps version 1 public +# - snmp-server host 192.0.2.1 source-interface Ethernet1/1 +# - snmp-server host 192.0.2.2 informs version 3 auth NMS +# - snmp-server community private group network-admin +# - snmp-server community public group network-operator +# - snmp-server enable traps aaa server-state-change +# - snmp-server enable traps system Clock-change-notification + +# Using parsed + +# parsed.cfg +# ------------ +# snmp-server contact nxosswitchadmin@localhost +# snmp-server location serverroom-1 +# snmp-server aaa-user cache-timeout 36000 +# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey +# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2 +# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4 +# snmp-server host 192.0.2.1 traps version 1 public +# snmp-server host 192.0.2.1 source-interface Ethernet1/1 +# snmp-server host 192.0.2.2 informs version 3 auth NMS +# snmp-server community private group network-admin +# snmp-server community public group network-operator +# snmp-server enable traps aaa server-state-change +# snmp-server enable traps system Clock-change-notification + +- name: Parse externally provided snmp-server configuration + cisco.nxos.nxos_snmp_server: + running_config: "{{ lookup('file', './parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- +# parsed: +# aaa_user: +# cache_timeout: 36000 +# communities: +# - community: private +# group: network-admin +# - community: public +# group: network-operator +# contact: nxosswitchadmin@localhost +# location: serverroom-1 +# traps: +# aaa: +# server_state_change: True +# system: +# clock_change_notification: True +# hosts: +# - host: 192.0.2.1 +# traps: true +# version: "1" +# community: public +# +# - host: 192.0.2.1 +# source_interface: Ethernet1/1 +# +# - host: 192.0.2.2 +# informs: true +# version: "3" +# auth: NMS +# users: +# auth: +# - user: snmp_user_1 +# group: network-operator +# authentication: +# algorithm: md5 +# password: "0x5632724fb8ac3699296af26281e1d0f1" +# localized_key: True +# +# - authentication: +# algorithm: md5 +# localized_key: true +# password: "0x5632724fb8ac3699296af26281e1d0f1" +# priv: +# aes_128: true +# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1" +# group: network-operator +# user: snmp_user_2 +# +# use_acls: +# - user: snmp_user_1 +# ipv4: acl1 +# ipv6: acl2 +# - user: snmp_user_2 +# ipv4: acl3 +# ipv6: acl4 +# +""" + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: dict + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: list + sample: + - sample command 1 + - sample command 2 + - sample command 3 +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - sample command 1 + - sample command 2 + - sample command 3 +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: list + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: list + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.snmp_server.snmp_server import ( + Snmp_serverArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.snmp_server.snmp_server import ( + Snmp_server, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Snmp_serverArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Snmp_server(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py new file mode 100644 index 00000000..fce1a8a2 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py @@ -0,0 +1,319 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_snmp_traps +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2024-01-01) Manages SNMP traps. +description: +- Manages SNMP traps configurations. +version_added: 1.0.0 +deprecated: + alternative: nxos_snmp_server + why: Updated modules released with more functionality + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +- This module works at the group level for traps. If you need to only enable/disable + 1 specific trap within a group, use the M(cisco.nxos.nxos_command) module. +- Be aware that you can set a trap only for an enabled feature. +options: + group: + description: + - Case sensitive group. + required: true + choices: + - aaa + - bfd + - bgp + - bridge + - callhome + - cfs + - config + - eigrp + - entity + - feature-control + - generic + - hsrp + - license + - link + - lldp + - mmode + - ospf + - pim + - rf + - rmon + - snmp + - storm-control + - stpx + - switchfabric + - syslog + - sysmgr + - system + - upgrade + - vtp + - all + type: str + state: + description: + - Manage the state of the resource. + required: false + default: enabled + choices: + - enabled + - disabled + type: str +""" + +EXAMPLES = """ +# ensure lldp trap configured +- cisco.nxos.nxos_snmp_traps: + group: lldp + state: enabled + +# ensure lldp trap is not configured +- cisco.nxos.nxos_snmp_traps: + group: lldp + state: disabled +""" + +RETURN = """ +commands: + description: command sent to the device + returned: always + type: list + sample: "snmp-server enable traps lldp ;" +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + load_config, + run_commands, +) + + +def get_platform_id(module): + info = get_capabilities(module).get("device_info", {}) + return info.get("network_os_platform", "") + + +def execute_show_command(command, module): + command = {"command": command, "output": "text"} + + return run_commands(module, command) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_snmp_traps(group, module): + body = execute_show_command("show run snmp all", module)[0].split("\n") + + resource = {} + feature_list = [ + "aaa", + "bfd", + "bgp", + "bridge", + "callhome", + "cfs", + "config", + "eigrp", + "entity", + "feature-control", + "generic", + "hsrp", + "license", + "link", + "lldp", + "mmode", + "ospf", + "pim", + "rf", + "rmon", + "snmp", + "storm-control", + "stpx", + "switchfabric", + "syslog", + "sysmgr", + "system", + "upgrade", + "vtp", + ] + + if "all" in group and "N3K-C35" in get_platform_id(module): + module.warn("Platform does not support bfd traps; bfd ignored for 'group: all' request") + feature_list.remove("bfd") + + for each in feature_list: + for line in body: + if each == "ospf": + # ospf behaves differently when routers are present + if "snmp-server enable traps ospf" == line: + resource[each] = True + break + else: + if "enable traps {0}".format(each) in line: + if "no " in line: + resource[each] = False + break + else: + resource[each] = True + + for each in feature_list: + if resource.get(each) is None: + # on some platforms, the 'no' cmd does not + # show up and so check if the feature is enabled + body = execute_show_command("show run | inc feature", module)[0] + if "feature {0}".format(each) in body: + resource[each] = False + + find = resource.get(group, None) + + if group == "all".lower(): + return resource + elif find is not None: + trap_resource = {group: find} + return trap_resource + else: + # if 'find' is None, it means that 'group' is a + # currently disabled feature. + return {} + + +def get_trap_commands(group, state, existing, module): + commands = [] + enabled = False + disabled = False + + if group == "all": + if state == "disabled": + for feature in existing: + if existing[feature]: + trap_command = "no snmp-server enable traps {0}".format(feature) + commands.append(trap_command) + + elif state == "enabled": + for feature in existing: + if existing[feature] is False: + trap_command = "snmp-server enable traps {0}".format(feature) + commands.append(trap_command) + + else: + if group in existing: + if existing[group]: + enabled = True + else: + disabled = True + + if state == "disabled" and enabled: + commands.append(["no snmp-server enable traps {0}".format(group)]) + elif state == "enabled" and disabled: + commands.append(["snmp-server enable traps {0}".format(group)]) + else: + module.fail_json(msg="{0} is not a currently " "enabled feature.".format(group)) + + return commands + + +def main(): + argument_spec = dict( + state=dict(choices=["enabled", "disabled"], default="enabled"), + group=dict( + choices=[ + "aaa", + "bfd", + "bgp", + "bridge", + "callhome", + "cfs", + "config", + "eigrp", + "entity", + "feature-control", + "generic", + "hsrp", + "license", + "link", + "lldp", + "mmode", + "ospf", + "pim", + "rf", + "rmon", + "snmp", + "storm-control", + "stpx", + "switchfabric", + "syslog", + "sysmgr", + "system", + "upgrade", + "vtp", + "all", + ], + required=True, + ), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + group = module.params["group"].lower() + state = module.params["state"] + + existing = get_snmp_traps(group, module) + + commands = get_trap_commands(group, state, existing, module) + cmds = flatten_list(commands) + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + + if "configure" in cmds: + cmds.pop(0) + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py new file mode 100644 index 00000000..a3b0b5fa --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py @@ -0,0 +1,412 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_snmp_user +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: (deprecated, removed after 2024-01-01) Manages SNMP users for monitoring. +description: +- Manages SNMP user configuration. +version_added: 1.0.0 +deprecated: + alternative: nxos_snmp_server + why: Updated modules released with more functionality + removed_at_date: '2024-01-01' +author: +- Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Limited Support for Cisco MDS +- Authentication parameters not idempotent. +options: + user: + description: + - Name of the user. + required: true + type: str + group: + description: + - Group to which the user will belong to. If state = present, and the user is + existing, the group is added to the user. If the user is not existing, user + entry is created with this group argument. If state = absent, only the group + is removed from the user entry. However, to maintain backward compatibility, + if the existing user belongs to only one group, and if group argument is same + as the existing user's group, then the user entry also is deleted. + type: str + authentication: + description: + - Authentication parameters for the user. + choices: + - md5 + - sha + type: str + pwd: + description: + - Authentication password when using md5 or sha. This is not idempotent + type: str + privacy: + description: + - Privacy password for the user. This is not idempotent + type: str + encrypt: + description: + - Enables AES-128 bit encryption when using privacy password. + type: bool + state: + description: + - Manage the state of the resource. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- cisco.nxos.nxos_snmp_user: + user: ntc + group: network-operator + authentication: md5 + pwd: test_password +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["snmp-server user ntc network-operator auth md5 test_password"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def execute_show_command(command, module, text=False): + command = {"command": command, "output": "json"} + if text: + command["output"] = "text" + + return run_commands(module, command) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_snmp_groups(module): + data = execute_show_command("show snmp group", module)[0] + group_list = [] + + try: + group_table = data["TABLE_role"]["ROW_role"] + for group in group_table: + group_list.append(group["role_name"]) + except (KeyError, AttributeError): + return group_list + + return group_list + + +def get_snmp_user(user, module): + command = "show snmp user {0}".format(user) + body = execute_show_command(command, module, text=True) + body_text = body[0] + + if "No such entry" not in body[0]: + body = execute_show_command(command, module) + + resource = {} + try: + # The TABLE and ROW keys differ between NXOS platforms. + if body[0].get("TABLE_snmp_user"): + tablekey = "TABLE_snmp_user" + rowkey = "ROW_snmp_user" + tablegrpkey = "TABLE_snmp_group_names" + rowgrpkey = "ROW_snmp_group_names" + authkey = "auth_protocol" + privkey = "priv_protocol" + grpkey = "group_names" + elif body[0].get("TABLE_snmp_users"): + tablekey = "TABLE_snmp_users" + rowkey = "ROW_snmp_users" + tablegrpkey = "TABLE_groups" + rowgrpkey = "ROW_groups" + authkey = "auth" + privkey = "priv" + grpkey = "group" + + rt = body[0][tablekey][rowkey] + # on some older platforms, all groups except the 1st one + # are in list elements by themselves and they are + # indexed by 'user'. This is due to a platform bug. + # Get first element if rt is a list due to the bug + # or if there is no bug, parse rt directly + if isinstance(rt, list): + resource_table = rt[0] + else: + resource_table = rt + + resource["user"] = user + resource["authentication"] = str(resource_table[authkey]).strip() + encrypt = str(resource_table[privkey]).strip() + if encrypt.startswith("aes"): + resource["encrypt"] = "aes-128" + else: + resource["encrypt"] = "none" + + groups = [] + if tablegrpkey in resource_table: + group_table = resource_table[tablegrpkey][rowgrpkey] + try: + for group in group_table: + groups.append(str(group[grpkey]).strip()) + except TypeError: + groups.append(str(group_table[grpkey]).strip()) + + # Now for the platform bug case, get the groups + if isinstance(rt, list): + # remove 1st element from the list as this is parsed already + rt.pop(0) + # iterate through other elements indexed by + # 'user' and add it to groups. + for each in rt: + groups.append(each["user"].strip()) + + # Some 'F' platforms use 'group' key instead + elif "group" in resource_table: + # single group is a string, multiple groups in a list + groups = resource_table["group"] + if isinstance(groups, str): + groups = [groups] + + resource["group"] = groups + + except (KeyError, AttributeError, IndexError, TypeError): + if not resource and body_text and "No such entry" not in body_text: + # 6K and other platforms may not return structured output; + # attempt to get state from text output + resource = get_non_structured_snmp_user(body_text) + + return resource + + +def get_non_structured_snmp_user(body_text): + # This method is a workaround for platforms that don't support structured + # output for 'show snmp user <foo>'. This workaround may not work on all + # platforms. Sample non-struct output: + # + # User Auth Priv(enforce) Groups acl_filter + # ____ ____ _____________ ______ __________ + # sample1 no no network-admin ipv4:my_acl + # network-operator + # priv-11 + # -OR- + # sample2 md5 des(no) priv-15 + # -OR- + # sample3 md5 aes-128(no) network-admin + resource = {} + output = body_text.rsplit("__________")[-1] + pat = re.compile( + r"^(?P<user>\S+)\s+" + r"(?P<auth>\S+)\s+" + r"(?P<priv>[\w\d-]+)(?P<enforce>\([\w\d-]+\))*\s+" + r"(?P<group>\S+)", + re.M, + ) + m = re.search(pat, output) + if not m: + return resource + resource["user"] = m.group("user") + resource["auth"] = m.group("auth") + resource["encrypt"] = "aes-128" if "aes" in str(m.group("priv")) else "none" + + resource["group"] = [m.group("group")] + more_groups = re.findall(r"^\s+([\w\d-]+)\s*$", output, re.M) + if more_groups: + resource["group"] += more_groups + + return resource + + +def remove_snmp_user(user, group=None): + if group: + return ["no snmp-server user {0} {1}".format(user, group)] + else: + return ["no snmp-server user {0}".format(user)] + + +def config_snmp_user(proposed, user, reset): + if reset: + commands = remove_snmp_user(user) + else: + commands = [] + + if proposed.get("group"): + cmd = "snmp-server user {0} {group}".format(user, **proposed) + else: + cmd = "snmp-server user {0}".format(user) + + auth = proposed.get("authentication", None) + pwd = proposed.get("pwd", None) + + if auth and pwd: + cmd += " auth {authentication} {pwd}".format(**proposed) + + encrypt = proposed.get("encrypt", None) + privacy = proposed.get("privacy", None) + + if encrypt and privacy: + cmd += " priv {encrypt} {privacy}".format(**proposed) + elif privacy: + cmd += " priv {privacy}".format(**proposed) + + if cmd: + commands.append(cmd) + + return commands + + +def main(): + argument_spec = dict( + user=dict(required=True, type="str"), + group=dict(type="str"), + pwd=dict(type="str", no_log=True), + privacy=dict(type="str"), + authentication=dict(choices=["md5", "sha"]), + encrypt=dict(type="bool"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=[["authentication", "pwd"], ["encrypt", "privacy"]], + supports_check_mode=True, + ) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + user = module.params["user"] + group = module.params["group"] + pwd = module.params["pwd"] + privacy = module.params["privacy"] + encrypt = module.params["encrypt"] + authentication = module.params["authentication"] + state = module.params["state"] + + if privacy and encrypt: + if not pwd and authentication: + module.fail_json( + msg="pwd and authentication must be provided " "when using privacy and encrypt", + ) + + if group and group not in get_snmp_groups(module): + module.fail_json(msg="group not configured yet on switch.") + + existing = get_snmp_user(user, module) + + if state == "present" and existing: + if group: + if group not in existing["group"]: + existing["group"] = None + else: + existing["group"] = group + else: + existing["group"] = None + + commands = [] + + if state == "absent" and existing: + if group: + if group in existing["group"]: + if len(existing["group"]) == 1: + commands.append(remove_snmp_user(user)) + else: + commands.append(remove_snmp_user(user, group)) + else: + commands.append(remove_snmp_user(user)) + + elif state == "present": + reset = False + + args = dict( + user=user, + pwd=pwd, + group=group, + privacy=privacy, + encrypt=encrypt, + authentication=authentication, + ) + proposed = dict((k, v) for k, v in args.items() if v is not None) + + if not existing: + if encrypt: + proposed["encrypt"] = "aes-128" + commands.append(config_snmp_user(proposed, user, reset)) + + elif existing: + if encrypt and not existing["encrypt"].startswith("aes"): + reset = True + proposed["encrypt"] = "aes-128" + + delta = dict(set(proposed.items()).difference(existing.items())) + + if delta.get("pwd"): + delta["authentication"] = authentication + + if delta and encrypt: + delta["encrypt"] = "aes-128" + + if delta: + command = config_snmp_user(delta, user, reset) + commands.append(command) + + cmds = flatten_list(commands) + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + + if "configure" in cmds: + cmds.pop(0) + results["commands"] = cmds + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py new file mode 100644 index 00000000..15b8e690 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py @@ -0,0 +1,477 @@ +#!/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 nxos_static_routes +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_static_routes +short_description: Static routes resource module +description: This module configures and manages the attributes of static routes on + Cisco NX-OS platforms. +version_added: 1.0.0 +author: Adharsh Srivats Rangarajan (@adharshsrivatsr) +notes: +- Tested against NX-OS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- When a route is configured for a non-existent VRF, the VRF is created and the route + is added to it. +- When deleting routes for a VRF, all routes inside the VRF are deleted, but the VRF + is not deleted. +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the following commands in order B(show running-config | include + '^ip(v6)* route') and B(show running-config | section '^vrf context'). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: + - A list of configurations for static routes + type: list + elements: dict + suboptions: + vrf: + description: + - The VRF to which the static route(s) belong + type: str + address_families: + description: A dictionary specifying the address family to which the static + route(s) belong. + type: list + elements: dict + suboptions: + afi: + description: + - Specifies the top level address family indicator. + type: str + choices: [ipv4, ipv6] + required: true + routes: + description: A dictionary that specifies the static route configurations + elements: dict + type: list + suboptions: + dest: + description: + - Destination prefix of static route + - The address format is <ipv4/v6 address>/<mask> + - The mask is number in range 0-32 for IPv4 and in range 0-128 for + IPv6 + type: str + required: true + next_hops: + description: + - Details of route to be taken + type: list + elements: dict + suboptions: + forward_router_address: + description: + - IP address of the next hop router + type: str + # required: True + interface: + description: + - Outgoing interface to take. For anything except 'Null0', then + next hop IP address should also be configured. + type: str + admin_distance: + description: + - Preference or administrative distance of route (range 1-255) + type: int + route_name: + description: + - Name of the static route + type: str + tag: + description: + - Route tag value (numeric) + type: int + track: + description: + - Track value (range 1 - 512). Track must already be configured + on the device before adding the route. + type: int + dest_vrf: + description: + - VRF of the destination + type: str + state: + description: + - The state the configuration should be left in + type: str + choices: + - deleted + - merged + - overridden + - replaced + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using deleted: + +# Before state: +# ------------- +# +# ip route 192.0.2.32/28 192.0.2.12 name new_route +# ip route 192.0.2.26/24 192.0.2.13 tag 12 + +- name: Delete all routes + cisco.nxos.nxos_static_routes: + state: deleted + +# After state: +# ------------ +# + + +# Before state: +# ------------ +# +# ip route 192.0.2.16/28 192.0.2.24 name new_route +# ip route 192.0.2.80/28 192.0.2.26 tag 12 +# vrf context trial_vrf +# ip route 192.0.2.64/28 192.0.2.22 tag 4 +# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1 +# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5 + +- name: Delete routes based on VRF + cisco.nxos.nxos_static_routes: + config: + - vrf: trial_vrf + state: deleted + +# After state: +# ----------- +# ip route 192.0.2.16/28 192.0.2.24 name new_route +# ip route 192.0.2.80/28 192.0.2.26 tag 12 +# vrf context trial_vrf + + +# Before state: +# ------------ +# +# ip route 192.0.2.16/28 192.0.2.24 name new_route +# ip route 192.0.2.80/28 192.0.2.26 tag 12 +# vrf context trial_vrf +# ip route 192.0.2.64/28 192.0.2.22 tag 4 +# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1 +# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5 + +- name: Delete routes based on AFI in a VRF + cisco.nxos.nxos_static_routes: + config: + - vrf: trial_vrf + address_families: + - afi: ipv4 + state: deleted + +# After state: +# ----------- +# ip route 192.0.2.16/28 192.0.2.24 name new_route +# ip route 192.0.2.80/28 192.0.2.26 tag 12 +# vrf context trial_vrf +# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5 + + +# Before state: +# ----------- +# ip route 192.0.2.16/28 192.0.2.24 name new_route +# vrf context trial_vrf +# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5 + + +# Using merged + +# Before state: +# ------------- +# + +- name: Merge new static route configuration + cisco.nxos.nxos_static_routes: + config: + - vrf: trial_vrf + address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.64/24 + next_hops: + - forward_router_address: 192.0.2.22 + tag: 4 + admin_distance: 2 + + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.16/24 + next_hops: + - forward_router_address: 192.0.2.24 + route_name: new_route + - afi: ipv6 + routes: + - dest: 2001:db8::/64 + next_hops: + - interface: eth1/3 + forward_router_address: 2001:db8::12 + state: merged + +# After state: +# ------------ +# +# ip route 192.0.2.16/24 192.0.2.24 name new_route +# ipv6 route 2001:db8::/64 Ethernet1/3 2001:db8::12 +# vrf context trial_vrf +# ip route 192.0.2.0/24 192.0.2.22 tag 4 2 + + +# Using overridden: + +# Before state: +# ------------- +# +# ip route 192.0.2.16/28 192.0.2.24 name new_route +# ip route 192.0.2.80/28 192.0.2.26 tag 12 +# vrf context trial_vrf +# ip route 192.0.2.64/28 192.0.2.22 tag 4 +# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1 + +- name: Overriden existing static route configuration with new configuration + cisco.nxos.nxos_static_routes: + config: + - vrf: trial_vrf + address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.16/28 + next_hops: + - forward_router_address: 192.0.2.23 + route_name: overridden_route1 + admin_distance: 3 + + - forward_router_address: 192.0.2.45 + route_name: overridden_route2 + dest_vrf: destinationVRF + interface: Ethernet1/2 + state: overridden + +# After state: +# ------------ +# +# ip route 192.0.2.16/28 192.0.2.23 name replaced_route1 3 +# ip route 192.0.2.16/28 Ethernet1/2 192.0.2.45 vrf destinationVRF name replaced_route2 + + +# Using replaced: + +# Before state: +# ------------ +# ip route 192.0.2.16/28 192.0.2.24 name new_route +# ip route 192.0.2.80/28 192.0.2.26 tag 12 +# vrf context trial_vrf +# ip route 192.0.2.64/28 192.0.2.22 tag 4 +# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1 + +- name: Replaced the existing static configuration of a prefix with new configuration + cisco.nxos.nxos_static_routes: + config: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.16/28 + next_hops: + - forward_router_address: 192.0.2.23 + route_name: replaced_route1 + admin_distance: 3 + + - forward_router_address: 192.0.2.45 + route_name: replaced_route2 + dest_vrf: destinationVRF + interface: Ethernet1/2 + state: replaced + +# After state: +# ----------- +# ip route 192.0.2.16/28 192.0.2.23 name replaced_route1 3 +# ip route 192.0.2.16/28 Ethernet1/2 192.0.2.45 vrf destinationVRF name replaced_route2 +# ip route 192.0.2.80/28 192.0.2.26 tag 12 +# vrf context trial_vrf +# ip route 192.0.2.64/28 192.0.2.22 tag 4 +# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1 + + +# Using gathered: + +# Before state: +# ------------- +# ipv6 route 2001:db8:12::/32 2001:db8::12 +# vrf context Test +# ip route 192.0.2.48/28 192.0.2.13 +# ip route 192.0.2.48/28 192.0.2.14 5 + +- name: Gather the exisitng condiguration + cisco.nxos.nxos_static_routes: + state: gathered + +# returns: +# gathered: +# - vrf: Test +# address_families: +# - afi: ipv4 +# routes: +# - dest: 192.0.2.48/28 +# next_hops: +# - forward_router_address: 192.0.2.13 +# +# - forward_router_address: 192.0.2.14 +# admin_distance: 5 +# +# - address_families: +# - afi: ipv6 +# routes: +# - dest: 2001:db8:12::/32 +# next_hops: +# - forward_router_address: 2001:db8::12 + + +# Using rendered: + +- name: Render required configuration to be pushed to the device + cisco.nxos.nxos_static_routes: + config: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.48/28 + next_hops: + - forward_router_address: 192.0.2.13 + + - afi: ipv6 + routes: + - dest: 2001:db8::/64 + next_hops: + - interface: eth1/3 + forward_router_address: 2001:db8::12 + state: rendered + +# returns +# rendered: +# vrf context default +# ip route 192.0.2.48/28 192.0.2.13 +# ipv6 route 2001:db8::/64 Ethernet1/3 2001:db8::12 + + +# Using parsed + +- name: Parse the config to structured data + cisco.nxos.nxos_static_routes: + running_config: | + ipv6 route 2002:db8:12::/32 2002:db8:12::1 + vrf context Test + ip route 192.0.2.48/28 192.0.2.13 + ip route 192.0.2.48/28 192.0.2.14 5 + +# returns: +# parsed: +# - vrf: Test +# address_families: +# - afi: ipv4 +# routes: +# - dest: 192.0.2.48/28 +# next_hops: +# - forward_router_address: 192.0.2.13 +# +# - forward_router_address: 192.0.2.14 +# admin_distance: 5 +# +# - address_families: +# - afi: ipv6 +# routes: +# - dest: 2002:db8:12::/32 +# next_hops: +# - forward_router_address: 2002:db8:12::1 + + +""" +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: ['ip route 192.0.2.48/28 192.0.2.12 Ethernet1/2 name sample_route', + 'ipv6 route 2001:db8:3000::/36 2001:db8:200:2::2', 'vrf context test','ip route 192.0.2.48/28 192.0.2.121'] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.static_routes.static_routes import ( + Static_routesArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.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/cisco/nxos/plugins/modules/nxos_system.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_system.py new file mode 100644 index 00000000..df4bbde0 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_system.py @@ -0,0 +1,399 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_system +extends_documentation_fragment: +- cisco.nxos.nxos +author: Peter Sprygada (@privateip) +short_description: Manage the system attributes on Cisco NXOS devices +notes: +- Unsupported for Cisco MDS +description: +- This module provides declarative management of node system attributes on Cisco NXOS + devices. It provides an option to configure host system parameters or remove those + parameters from the device active configuration. +version_added: 1.0.0 +options: + hostname: + description: + - Configure the device hostname parameter. This option takes an ASCII string value + or keyword 'default' + type: str + domain_name: + description: + - Configures the default domain name suffix to be used when referencing this node + by its FQDN. This argument accepts either a list of domain names or a list + of dicts that configure the domain name and VRF name or keyword 'default'. See + examples. + type: list + elements: raw + domain_lookup: + description: + - Enables or disables the DNS lookup feature in Cisco NXOS. This argument accepts + boolean values. When enabled, the system will try to resolve hostnames using + DNS and when disabled, hostnames will not be resolved. + type: bool + domain_search: + description: + - Configures a list of domain name suffixes to search when performing DNS name + resolution. This argument accepts either a list of domain names or a list of + dicts that configure the domain name and VRF name or keyword 'default'. See + examples. + type: list + elements: raw + name_servers: + description: + - List of DNS name servers by IP address to use to perform name resolution lookups. This + argument accepts either a list of DNS servers or a list of hashes that configure + the name server and VRF name or keyword 'default'. See examples. + type: list + elements: raw + system_mtu: + description: + - Specifies the mtu, must be an integer or keyword 'default'. + type: str + state: + description: + - State of the configuration values in the device's current active configuration. When + set to I(present), the values should be configured in the device active configuration + and when set to I(absent) the values should not be in the device active configuration + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- name: configure hostname and domain-name + cisco.nxos.nxos_system: + hostname: nxos01 + domain_name: test.example.com + +- name: remove configuration + cisco.nxos.nxos_system: + state: absent + +- name: configure name servers + cisco.nxos.nxos_system: + name_servers: + - 8.8.8.8 + - 8.8.4.4 + +- name: configure name servers with VRF support + cisco.nxos.nxos_system: + name_servers: + - {server: 8.8.8.8, vrf: mgmt} + - {server: 8.8.4.4, vrf: mgmt} +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - hostname nxos01 + - ip domain-name test.example.com +""" +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + ComplexList, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +_CONFIGURED_VRFS = None + + +def has_vrf(module, vrf): + global _CONFIGURED_VRFS + if _CONFIGURED_VRFS is not None: + return vrf in _CONFIGURED_VRFS + config = get_config(module) + _CONFIGURED_VRFS = re.findall(r"vrf context (\S+)", config) + return vrf in _CONFIGURED_VRFS + + +def map_obj_to_commands(want, have, module): + commands = list() + state = module.params["state"] + + def needs_update(x): + return want.get(x) and (want.get(x) != have.get(x)) + + def difference(x, y, z): + return [item for item in x[z] if item not in y[z]] + + def remove(cmd, commands, vrf=None): + if vrf: + commands.append("vrf context %s" % vrf) + commands.append(cmd) + if vrf: + commands.append("exit") + + def add(cmd, commands, vrf=None): + if vrf: + if not has_vrf(module, vrf): + module.fail_json(msg="invalid vrf name %s" % vrf) + return remove(cmd, commands, vrf) + + if state == "absent": + if have["hostname"]: + commands.append("no hostname") + + for item in have["domain_name"]: + cmd = "no ip domain-name %s" % item["name"] + remove(cmd, commands, item["vrf"]) + + for item in have["domain_search"]: + cmd = "no ip domain-list %s" % item["name"] + remove(cmd, commands, item["vrf"]) + + for item in have["name_servers"]: + cmd = "no ip name-server %s" % item["server"] + remove(cmd, commands, item["vrf"]) + + if have["system_mtu"]: + commands.append("no system jumbomtu") + + if state == "present": + if needs_update("hostname"): + if want["hostname"] == "default": + if have["hostname"]: + commands.append("no hostname") + else: + commands.append("hostname %s" % want["hostname"]) + + if want.get("domain_lookup") is not None: + if have.get("domain_lookup") != want.get("domain_lookup"): + cmd = "ip domain-lookup" + if want["domain_lookup"] is False: + cmd = "no %s" % cmd + commands.append(cmd) + + if want["domain_name"]: + if want.get("domain_name")[0]["name"] == "default": + if have["domain_name"]: + for item in have["domain_name"]: + cmd = "no ip domain-name %s" % item["name"] + remove(cmd, commands, item["vrf"]) + else: + for item in difference(have, want, "domain_name"): + cmd = "no ip domain-name %s" % item["name"] + remove(cmd, commands, item["vrf"]) + for item in difference(want, have, "domain_name"): + cmd = "ip domain-name %s" % item["name"] + add(cmd, commands, item["vrf"]) + + if want["domain_search"]: + if want.get("domain_search")[0]["name"] == "default": + if have["domain_search"]: + for item in have["domain_search"]: + cmd = "no ip domain-list %s" % item["name"] + remove(cmd, commands, item["vrf"]) + else: + for item in difference(have, want, "domain_search"): + cmd = "no ip domain-list %s" % item["name"] + remove(cmd, commands, item["vrf"]) + for item in difference(want, have, "domain_search"): + cmd = "ip domain-list %s" % item["name"] + add(cmd, commands, item["vrf"]) + + if want["name_servers"]: + if want.get("name_servers")[0]["server"] == "default": + if have["name_servers"]: + for item in have["name_servers"]: + cmd = "no ip name-server %s" % item["server"] + remove(cmd, commands, item["vrf"]) + else: + for item in difference(have, want, "name_servers"): + cmd = "no ip name-server %s" % item["server"] + remove(cmd, commands, item["vrf"]) + for item in difference(want, have, "name_servers"): + cmd = "ip name-server %s" % item["server"] + add(cmd, commands, item["vrf"]) + + if needs_update("system_mtu"): + if want["system_mtu"] == "default": + if have["system_mtu"]: + commands.append("no system jumbomtu") + else: + commands.append("system jumbomtu %s" % want["system_mtu"]) + + return commands + + +def parse_hostname(config): + match = re.search(r"^hostname (\S+)", config, re.M) + if match: + return match.group(1) + + +def parse_domain_name(config, vrf_config): + objects = list() + match = re.search(r"^ip domain-name (\S+)", config, re.M) + if match: + objects.append({"name": match.group(1), "vrf": None}) + + for vrf, cfg in iteritems(vrf_config): + match = re.search(r"ip domain-name (\S+)", cfg, re.M) + if match: + objects.append({"name": match.group(1), "vrf": vrf}) + + return objects + + +def parse_domain_search(config, vrf_config): + objects = list() + + for item in re.findall(r"^ip domain-list (\S+)", config, re.M): + objects.append({"name": item, "vrf": None}) + + for vrf, cfg in iteritems(vrf_config): + for item in re.findall(r"ip domain-list (\S+)", cfg, re.M): + objects.append({"name": item, "vrf": vrf}) + + return objects + + +def parse_name_servers(config, vrf_config, vrfs): + objects = list() + + match = re.search("^ip name-server (.+)$", config, re.M) + if match and "use-vrf" not in match.group(1): + for addr in match.group(1).split(" "): + objects.append({"server": addr, "vrf": None}) + + for vrf, cfg in iteritems(vrf_config): + vrf_match = re.search("ip name-server (.+)", cfg, re.M) + if vrf_match: + for addr in vrf_match.group(1).split(" "): + objects.append({"server": addr, "vrf": vrf}) + + return objects + + +def parse_system_mtu(config): + match = re.search(r"^system jumbomtu (\d+)", config, re.M) + if match: + return match.group(1) + + +def map_config_to_obj(module): + config = get_config(module) + configobj = NetworkConfig(indent=2, contents=config) + + vrf_config = {} + + vrfs = re.findall(r"^vrf context (\S+)$", config, re.M) + for vrf in vrfs: + config_data = configobj.get_block_config(path=["vrf context %s" % vrf]) + vrf_config[vrf] = config_data + + return { + "hostname": parse_hostname(config), + "domain_lookup": "no ip domain-lookup" not in config, + "domain_name": parse_domain_name(config, vrf_config), + "domain_search": parse_domain_search(config, vrf_config), + "name_servers": parse_name_servers(config, vrf_config, vrfs), + "system_mtu": parse_system_mtu(config), + } + + +def map_params_to_obj(module): + obj = { + "hostname": module.params["hostname"], + "domain_lookup": module.params["domain_lookup"], + "system_mtu": module.params["system_mtu"], + } + + domain_name = ComplexList(dict(name=dict(key=True), vrf=dict()), module) + + domain_search = ComplexList(dict(name=dict(key=True), vrf=dict()), module) + + name_servers = ComplexList(dict(server=dict(key=True), vrf=dict()), module) + + for arg, cast in [ + ("domain_name", domain_name), + ("domain_search", domain_search), + ("name_servers", name_servers), + ]: + if module.params[arg] is not None: + obj[arg] = cast(module.params[arg]) + else: + obj[arg] = None + + return obj + + +def main(): + """main entry point for module execution""" + argument_spec = dict( + hostname=dict(), + domain_lookup=dict(type="bool"), + # { name: <str>, vrf: <str> } + domain_name=dict(type="list", elements="raw"), + # {name: <str>, vrf: <str> } + domain_search=dict(type="list", elements="raw"), + # { server: <str>; vrf: <str> } + name_servers=dict(type="list", elements="raw"), + system_mtu=dict(type="str"), + state=dict(default="present", choices=["present", "absent"]), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + result = {"changed": False} + if warnings: + result["warnings"] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(want, have, module) + result["commands"] = commands + + if commands: + if not module.check_mode: + load_config(module, commands) + result["changed"] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py new file mode 100644 index 00000000..7498ff88 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py @@ -0,0 +1,339 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Cisco and/or its affiliates. +# 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 nxos_telemetry +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_telemetry +short_description: TELEMETRY resource module +description: Manages Telemetry Monitoring Service (TMS) configuration +version_added: 1.0.0 +author: Mike Wiebe (@mikewiebe) +notes: +- Supported on N9k Version 7.0(3)I7(5) and later. +- Unsupported for Cisco MDS +options: + config: + description: The provided configuration + type: dict + suboptions: + certificate: + type: dict + description: + - Certificate SSL/TLS and hostname values. + - Value must be a dict defining values for keys (key and hostname). + suboptions: + key: + description: + - Certificate key + type: str + hostname: + description: + - Certificate hostname + type: str + compression: + type: str + description: + - Destination profile compression method. + choices: + - gzip + source_interface: + type: str + description: + - Destination profile source interface. + - Valid value is a str representing the source interface name. + vrf: + type: str + description: + - Destination profile vrf. + - Valid value is a str representing the vrf name. + destination_groups: + type: list + description: + - List of telemetry destination groups. + elements: raw + suboptions: + id: + type: str + description: + - Destination group identifier. + - Value must be an integer or string representing the destination group identifier. + destination: + type: dict + description: + - Group destination ipv4, port, protocol and encoding values. + - Value must be a dict defining values for keys (ip, port, protocol, encoding). + suboptions: + ip: + type: str + description: + - Destination group IP address. + port: + type: int + description: + - Destination group port number. + protocol: + type: str + description: + - Destination group protocol. + choices: + - HTTP + - TCP + - UDP + - gRPC + encoding: + type: str + description: + - Destination group encoding. + choices: + - GPB + - JSON + sensor_groups: + type: list + description: + - List of telemetry sensor groups. + elements: raw + suboptions: + id: + type: str + description: + - Sensor group identifier. + - Value must be a integer or a string representing the sensor group identifier. + data_source: + type: str + description: + - Telemetry data source. + choices: + - NX-API + - DME + - YANG + path: + type: dict + description: + - Telemetry sensor path. + - Value must be a dict defining values for keys (name, depth, filter_condition, + query_condition). + - Mandatory Keys (name) + - Optional Keys (depth, filter_condition, query_condition) + suboptions: + name: + type: str + description: + - Sensor group path name. + depth: + type: str + description: + - Sensor group depth. + filter_condition: + type: str + description: + - Sensor group filter condition. + query_condition: + type: str + description: + - Sensor group query condition. + subscriptions: + type: list + description: + - List of telemetry subscriptions. + elements: raw + suboptions: + id: + type: str + description: + - Subscription identifier. + - Value must be an integer or string representing the subscription identifier. + destination_group: + type: str + description: + - Associated destination group. + sensor_group: + type: dict + description: + - Associated sensor group. + - Value must be a dict defining values for keys (id, sample_interval). + suboptions: + id: + type: str + description: + - Associated sensor group id. + sample_interval: + type: int + description: + - Associated sensor group id sample interval. + state: + description: + - Final configuration state + type: str + choices: + - merged + - replaced + - deleted + - gathered + default: merged + +""" +EXAMPLES = """ +# Using deleted +# This action will delete all telemetry configuration on the device + +- name: Delete Telemetry Configuration + cisco.nxos.nxos_telemetry: + state: deleted + + +# Using merged +# This action will merge telemetry configuration defined in the playbook with +# telemetry configuration that is already on the device. + +- name: Merge Telemetry Configuration + cisco.nxos.nxos_telemetry: + config: + certificate: + key: /bootflash/server.key + hostname: localhost + compression: gzip + source_interface: Ethernet1/1 + vrf: management + destination_groups: + - id: 2 + destination: + ip: 192.168.0.2 + port: 50001 + protocol: gRPC + encoding: GPB + - id: 55 + destination: + ip: 192.168.0.55 + port: 60001 + protocol: gRPC + encoding: GPB + sensor_groups: + - id: 1 + data_source: NX-API + path: + name: '"show lldp neighbors detail"' + depth: 0 + - id: 55 + data_source: DME + path: + name: sys/ch + depth: unbounded + filter_condition: ne(eqptFt.operSt,"ok") + subscriptions: + - id: 5 + destination_group: 55 + sensor_group: + id: 1 + sample_interval: 1000 + - id: 6 + destination_group: 2 + sensor_group: + id: 55 + sample_interval: 2000 + state: merged + + +# Using replaced +# This action will replace telemetry configuration on the device with the +# telemetry configuration defined in the playbook. + +- name: Override Telemetry Configuration + cisco.nxos.nxos_telemetry: + config: + certificate: + key: /bootflash/server.key + hostname: localhost + compression: gzip + source_interface: Ethernet1/1 + vrf: management + destination_groups: + - id: 2 + destination: + ip: 192.168.0.2 + port: 50001 + protocol: gRPC + encoding: GPB + subscriptions: + - id: 5 + destination_group: 55 + state: replaced + + +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.telemetry.telemetry import ( + TelemetryArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.telemetry.telemetry import ( + Telemetry, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=TelemetryArgs.argument_spec, supports_check_mode=True) + + result = Telemetry(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py new file mode 100644 index 00000000..83955e15 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py @@ -0,0 +1,254 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_udld +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages UDLD global configuration params. +description: +- Manages UDLD global configuration params. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- Module will fail if the udld feature has not been previously enabled. +options: + aggressive: + description: + - Toggles aggressive mode. + choices: + - enabled + - disabled + type: str + msg_time: + description: + - Message time in seconds for UDLD packets or keyword 'default'. + type: str + reset: + description: + - Ability to reset all ports shut down by UDLD. 'state' parameter cannot be 'absent' + when this is present. + type: bool + state: + description: + - Manage the state of the resource. When set to 'absent', aggressive and msg_time + are set to their default values. + default: present + choices: + - present + - absent + type: str +""" +EXAMPLES = """ +# ensure udld aggressive mode is globally disabled and se global message interval is 20 +- cisco.nxos.nxos_udld: + aggressive: disabled + msg_time: 20 + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' + +# Ensure agg mode is globally enabled and msg time is 15 +- cisco.nxos.nxos_udld: + aggressive: enabled + msg_time: 15 + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +""" + +RETURN = """ +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"aggressive": "enabled", "msg_time": "40"} +existing: + description: + - k/v pairs of existing udld configuration + returned: always + type: dict + sample: {"aggressive": "disabled", "msg_time": "15"} +end_state: + description: k/v pairs of udld configuration after module execution + returned: always + type: dict + sample: {"aggressive": "enabled", "msg_time": "40"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["udld message-time 40", "udld aggressive"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +PARAM_TO_DEFAULT_KEYMAP = {"msg_time": "15"} + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if new_key: + value = table.get(key) + if value: + new_dict[new_key] = str(value) + else: + new_dict[new_key] = value + return new_dict + + +def get_commands_config_udld_global(delta, reset, existing): + commands = [] + for param, value in delta.items(): + if param == "aggressive": + command = "udld aggressive" if value == "enabled" else "no udld aggressive" + commands.append(command) + elif param == "msg_time": + if value == "default": + if existing.get("msg_time") != PARAM_TO_DEFAULT_KEYMAP.get("msg_time"): + commands.append("no udld message-time") + else: + commands.append("udld message-time " + value) + if reset: + command = "udld reset" + commands.append(command) + return commands + + +def get_commands_remove_udld_global(existing): + commands = [] + if existing.get("aggressive") == "enabled": + command = "no udld aggressive" + commands.append(command) + if existing.get("msg_time") != PARAM_TO_DEFAULT_KEYMAP.get("msg_time"): + command = "no udld message-time" + commands.append(command) + return commands + + +def get_udld_global(module): + command = "show udld global | json" + udld_table = run_commands(module, [command])[0] + + status = str(udld_table.get("udld-global-mode", None)) + if status == "enabled-aggressive": + aggressive = "enabled" + else: + aggressive = "disabled" + + interval = str(udld_table.get("message-interval", None)) + udld = dict(msg_time=interval, aggressive=aggressive) + + return udld + + +def main(): + argument_spec = dict( + aggressive=dict(required=False, choices=["enabled", "disabled"]), + msg_time=dict(required=False, type="str"), + reset=dict(required=False, type="bool"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + aggressive = module.params["aggressive"] + msg_time = module.params["msg_time"] + reset = module.params["reset"] + state = module.params["state"] + + if reset and state == "absent": + module.fail_json(msg="state must be present when using reset flag.") + + args = dict(aggressive=aggressive, msg_time=msg_time, reset=reset) + proposed = dict((k, v) for k, v in args.items() if v is not None) + + existing = get_udld_global(module) + end_state = existing + + delta = set(proposed.items()).difference(existing.items()) + changed = False + + commands = [] + if state == "present": + if delta: + command = get_commands_config_udld_global(dict(delta), reset, existing) + commands.append(command) + + elif state == "absent": + command = get_commands_remove_udld_global(existing) + if command: + commands.append(command) + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + changed = True + load_config(module, cmds) + end_state = get_udld_global(module) + if "configure" in cmds: + cmds.pop(0) + + results = {} + results["proposed"] = proposed + results["existing"] = existing + results["end_state"] = end_state + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py new file mode 100644 index 00000000..b78ed5c7 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_udld_interface +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages UDLD interface configuration params. +description: +- Manages UDLD interface configuration params. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- Feature UDLD must be enabled on the device to use this module. +options: + mode: + description: + - Manages UDLD mode for an interface. + required: true + choices: + - enabled + - disabled + - aggressive + type: str + interface: + description: + - FULL name of the interface, i.e. Ethernet1/1- + required: true + type: str + state: + description: + - Manage the state of the resource. + required: false + default: present + choices: + - present + - absent + type: str +""" +EXAMPLES = """ +# ensure Ethernet1/1 is configured to be in aggressive mode +- cisco.nxos.nxos_udld_interface: + interface: Ethernet1/1 + mode: aggressive + state: present + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' + +# Remove the aggressive config only if it's currently in aggressive mode and then disable udld (switch default) +- cisco.nxos.nxos_udld_interface: + interface: Ethernet1/1 + mode: aggressive + state: absent + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' + +# ensure Ethernet1/1 has aggressive mode enabled +- cisco.nxos.nxos_udld_interface: + interface: Ethernet1/1 + mode: enabled + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +""" + +RETURN = """ +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"mode": "enabled"} +existing: + description: + - k/v pairs of existing configuration + returned: always + type: dict + sample: {"mode": "aggressive"} +end_state: + description: k/v pairs of configuration after module execution + returned: always + type: dict + sample: {"mode": "enabled"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["interface ethernet1/33", + "no udld aggressive ; no udld disable"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_udld_interface(module, interface): + command = "show run udld all | section " + interface.title() + "$" + interface_udld = {} + mode = None + mode_str = None + try: + body = run_commands(module, [{"command": command, "output": "text"}])[0] + if "aggressive" in body: + mode = "aggressive" + mode_str = "aggressive" + elif "no udld enable" in body: + mode = "disabled" + mode_str = "no udld enable" + elif "no udld disable" in body: + mode = "enabled" + mode_str = "no udld disable" + elif "udld disable" in body: + mode = "disabled" + mode_str = "udld disable" + elif "udld enable" in body: + mode = "enabled" + mode_str = "udld enable" + interface_udld["mode"] = mode + + except (KeyError, AttributeError, IndexError): + interface_udld = {} + + return interface_udld, mode_str + + +def get_commands_config_udld_interface1(delta, interface, module, existing): + commands = [] + mode = delta["mode"] + if mode == "aggressive": + commands.append("udld aggressive") + else: + commands.append("no udld aggressive") + commands.insert(0, "interface {0}".format(interface)) + + return commands + + +def get_commands_config_udld_interface2(delta, interface, module, existing): + commands = [] + existing, mode_str = get_udld_interface(module, interface) + mode = delta["mode"] + if mode == "enabled": + if mode_str == "no udld enable": + command = "udld enable" + else: + command = "no udld disable" + else: + if mode_str == "no udld disable": + command = "udld disable" + else: + command = "no udld enable" + if command: + commands.append(command) + commands.insert(0, "interface {0}".format(interface)) + + return commands + + +def get_commands_remove_udld_interface(delta, interface, module, existing): + commands = [] + existing, mode_str = get_udld_interface(module, interface) + + mode = delta["mode"] + if mode == "aggressive": + command = "no udld aggressive" + else: + if mode == "enabled": + if mode_str == "udld enable": + command = "no udld enable" + else: + command = "udld disable" + elif mode == "disabled": + if mode_str == "no udld disable": + command = "udld disable" + else: + command = "no udld enable" + if command: + commands.append(command) + commands.insert(0, "interface {0}".format(interface)) + + return commands + + +def main(): + argument_spec = dict( + mode=dict(choices=["enabled", "disabled", "aggressive"], required=True), + interface=dict(type="str", required=True), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + interface = module.params["interface"].lower() + mode = module.params["mode"] + state = module.params["state"] + + proposed = dict(mode=mode) + existing, mode_str = get_udld_interface(module, interface) + end_state = existing + + delta = dict(set(proposed.items()).difference(existing.items())) + + changed = False + commands = [] + cmds = [] + if state == "present": + if delta: + command = get_commands_config_udld_interface1(delta, interface, module, existing) + commands.append(command) + cmds = flatten_list(commands) + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + changed = True + load_config(module, cmds) + + if delta["mode"] == "enabled" or delta["mode"] == "disabled": + commands = [] + command = get_commands_config_udld_interface2(delta, interface, module, existing) + commands.append(command) + cmds = flatten_list(commands) + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + load_config(module, cmds) + + else: + common = set(proposed.items()).intersection(existing.items()) + if common: + command = get_commands_remove_udld_interface(dict(common), interface, module, existing) + cmds = flatten_list(commands) + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + changed = True + load_config(module, cmds) + + if not module.check_mode: + end_state, mode_str = get_udld_interface(module, interface) + if "configure" in cmds: + cmds.pop(0) + + results = {} + results["proposed"] = proposed + results["existing"] = existing + results["end_state"] = end_state + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py new file mode 100644 index 00000000..83c22a76 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py @@ -0,0 +1,473 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_user +extends_documentation_fragment: +- cisco.nxos.nxos +author: Peter Sprygada (@privateip) +notes: +- Limited Support for Cisco MDS +short_description: Manage the collection of local users on Nexus devices +description: +- This module provides declarative management of the local usernames configured on + Cisco Nexus devices. It allows playbooks to manage either individual usernames + or the collection of usernames in the current running config. It also supports + purging usernames from the configuration that are not explicitly defined. +version_added: 1.0.0 +options: + aggregate: + description: + - The set of username objects to be configured on the remote Cisco Nexus device. The + list entries can either be the username or a hash of username and properties. This + argument is mutually exclusive with the C(name) argument. + aliases: + - users + - collection + type: list + elements: dict + suboptions: + name: + description: + - The username to be configured on the remote Cisco Nexus device. This argument + accepts a string value and is mutually exclusive with the C(aggregate) argument. + type: str + configured_password: + description: + - The password to be configured on the network device. The password needs to be + provided in cleartext and it will be encrypted on the device. + type: str + update_password: + description: + - Since passwords are encrypted in the device running config, this argument will + instruct the module when to change the password. When set to C(always), the + password will always be updated in the device and when set to C(on_create) the + password will be updated only if the username is created. + choices: + - on_create + - always + type: str + roles: + description: + - The C(role) argument configures the role for the username in the device running + configuration. The argument accepts a string value defining the role name. This + argument does not check if the role has been configured on the device. + aliases: + - role + type: list + elements: str + sshkey: + description: + - The C(sshkey) argument defines the SSH public key to configure for the username. This + argument accepts a valid SSH key value. + type: str + state: + description: + - The C(state) argument configures the state of the username definition as it + relates to the device operational configuration. When set to I(present), the + username(s) should be configured in the device active configuration and when + set to I(absent) the username(s) should not be in the device active configuration + choices: + - present + - absent + type: str + name: + description: + - The username to be configured on the remote Cisco Nexus device. This argument + accepts a string value and is mutually exclusive with the C(aggregate) argument. + type: str + configured_password: + description: + - The password to be configured on the network device. The password needs to be + provided in cleartext and it will be encrypted on the device. + type: str + update_password: + description: + - Since passwords are encrypted in the device running config, this argument will + instruct the module when to change the password. When set to C(always), the + password will always be updated in the device and when set to C(on_create) the + password will be updated only if the username is created. + default: always + choices: + - on_create + - always + type: str + roles: + description: + - The C(role) argument configures the role for the username in the device running + configuration. The argument accepts a string value defining the role name. This + argument does not check if the role has been configured on the device. + aliases: + - role + type: list + elements: str + sshkey: + description: + - The C(sshkey) argument defines the SSH public key to configure for the username. This + argument accepts a valid SSH key value. + type: str + purge: + description: + - The C(purge) argument instructs the module to consider the resource definition + absolute. It will remove any previously configured usernames on the device + with the exception of the `admin` user which cannot be deleted per nxos constraints. + type: bool + default: no + state: + description: + - The C(state) argument configures the state of the username definition as it + relates to the device operational configuration. When set to I(present), the + username(s) should be configured in the device active configuration and when + set to I(absent) the username(s) should not be in the device active configuration + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- name: create a new user + cisco.nxos.nxos_user: + name: ansible + sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" + state: present + +- name: remove all users except admin + cisco.nxos.nxos_user: + purge: yes + +- name: set multiple users role + cisco.nxos.nxos_user: + aggregate: + - name: netop + - name: netend + role: network-operator + state: present +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - name ansible + - name ansible password password +""" +import re + +from copy import deepcopy +from functools import partial + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_default_spec, + to_list, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, + run_commands, +) + + +BUILTIN_ROLES = [ + "network-admin", + "network-operator", + "vdc-admin", + "vdc-operator", + "priv-15", + "priv-14", + "priv-13", + "priv-12", + "priv-11", + "priv-10", + "priv-9", + "priv-8", + "priv-7", + "priv-6", + "priv-5", + "priv-4", + "priv-3", + "priv-2", + "priv-1", + "priv-0", +] + + +def get_custom_roles(module): + return re.findall( + r"^role name (\S+)", + get_config(module, flags=["| include '^role name'"]), + re.M, + ) + + +def validate_roles(value, module): + valid_roles = BUILTIN_ROLES + get_custom_roles(module) + for item in value: + if item not in valid_roles: + module.fail_json(msg="invalid role specified") + + +def map_obj_to_commands(updates, module): + commands = list() + update_password = module.params["update_password"] + + for update in updates: + want, have = update + + def needs_update(x): + return want.get(x) and (want.get(x) != have.get(x)) + + def add(x): + return commands.append("username %s %s" % (want["name"], x)) + + def remove(x): + return commands.append("no username %s %s" % (want["name"], x)) + + def configure_roles(): + if want["roles"]: + if have: + for item in set(have["roles"]).difference(want["roles"]): + remove("role %s" % item) + + for item in set(want["roles"]).difference(have["roles"]): + add("role %s" % item) + else: + for item in want["roles"]: + add("role %s" % item) + + return True + return False + + if want["state"] == "absent": + commands.append("no username %s" % want["name"]) + continue + + roles_configured = False + if want["state"] == "present" and not have: + roles_configured = configure_roles() + if not roles_configured: + commands.append("username %s" % want["name"]) + + if needs_update("configured_password"): + if update_password == "always" or not have: + add("password %s" % want["configured_password"]) + + if needs_update("sshkey"): + add("sshkey %s" % want["sshkey"]) + + if not roles_configured: + configure_roles() + + return commands + + +def parse_password(data): + if not data.get("remote_login"): + return "<PASSWORD>" + + +def parse_roles(data): + configured_roles = None + if "TABLE_role" in data: + configured_roles = data.get("TABLE_role")["ROW_role"] + + roles = list() + if configured_roles: + for item in to_list(configured_roles): + roles.append(item["role"]) + return roles + + +def map_config_to_obj(module): + out = run_commands(module, [{"command": "show user-account", "output": "json"}]) + data = out[0] + + objects = list() + + for item in to_list(data["TABLE_template"]["ROW_template"]): + objects.append( + { + "name": item["usr_name"], + "configured_password": parse_password(item), + "sshkey": item.get("sshkey_info"), + "roles": parse_roles(item), + "state": "present", + }, + ) + return objects + + +def get_param_value(key, item, module): + # if key doesn't exist in the item, get it from module.params + if not item.get(key): + value = module.params[key] + + # if key does exist, do a type check on it to validate it + else: + value_type = module.argument_spec[key].get("type", "str") + type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type] + type_checker(item[key]) + value = item[key] + + return value + + +def map_params_to_obj(module): + aggregate = module.params["aggregate"] + if not aggregate: + if not module.params["name"] and module.params["purge"]: + return list() + elif not module.params["name"]: + module.fail_json(msg="username is required") + else: + collection = [{"name": module.params["name"]}] + else: + collection = list() + for item in aggregate: + if not isinstance(item, dict): + collection.append({"name": item}) + elif "name" not in item: + module.fail_json(msg="name is required") + else: + collection.append(item) + + objects = list() + + for item in collection: + get_value = partial(get_param_value, item=item, module=module) + item.update( + { + "configured_password": get_value("configured_password"), + "sshkey": get_value("sshkey"), + "roles": get_value("roles"), + "state": get_value("state"), + }, + ) + + for key, value in iteritems(item): + if value: + # validate the param value (if validator func exists) + validator = globals().get("validate_%s" % key) + if all((value, validator)): + validator(value, module) + + objects.append(item) + + return objects + + +def update_objects(want, have): + updates = list() + for entry in want: + item = next((i for i in have if i["name"] == entry["name"]), None) + if all((item is None, entry["state"] == "present")): + updates.append((entry, {})) + elif item: + for key, value in iteritems(entry): + if value and value != item[key]: + updates.append((entry, item)) + return updates + + +def main(): + """main entry point for module execution""" + element_spec = dict( + name=dict(), + configured_password=dict(no_log=True), + update_password=dict(default="always", choices=["on_create", "always"]), + roles=dict(type="list", aliases=["role"], elements="str"), + sshkey=dict(no_log=False), + state=dict(default="present", choices=["present", "absent"]), + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict( + type="list", + elements="dict", + options=aggregate_spec, + aliases=["collection", "users"], + ), + purge=dict(type="bool", default=False), + ) + + argument_spec.update(element_spec) + + mutually_exclusive = [("name", "aggregate")] + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = {"changed": False, "warnings": []} + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands(update_objects(want, have), module) + + if module.params["purge"]: + want_users = [x["name"] for x in want] + have_users = [x["name"] for x in have] + for item in set(have_users).difference(want_users): + if item != "admin": + item = item.replace("\\", "\\\\") + commands.append("no username %s" % item) + + result["commands"] = commands + + # the nxos cli prevents this by rule so capture it and display + # a nice failure message + if "no username admin" in commands: + module.fail_json(msg="cannot delete the `admin` account") + + if commands: + if not module.check_mode: + responses = load_config(module, commands) + for resp in responses: + if resp.lower().startswith("wrong password"): + module.fail_json(msg=resp) + else: + result["warnings"].extend( + [x[9:] for x in resp.splitlines() if x.startswith("WARNING: ")], + ) + + result["changed"] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py new file mode 100644 index 00000000..cac276b6 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py @@ -0,0 +1,439 @@ +#!/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 nxos_vlans +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +module: nxos_vlans +short_description: VLANs resource module +description: This module creates and manages VLAN configurations on Cisco NX-OS. +version_added: 1.0.0 +author: Trishna Guha (@trishnaguha) +notes: +- Tested against NXOS 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the NX-OS device + by executing the commands B(show vlans | json-pretty) and B(show running-config + | section ^vlan) in order and delimited by a line. + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + config: + description: A dictionary of Vlan options + type: list + elements: dict + suboptions: + vlan_id: + description: + - Vlan ID. + type: int + required: true + name: + description: + - Name of VLAN. + type: str + state: + description: + - Manage operational state of the vlan. + type: str + choices: + - active + - suspend + enabled: + description: + - Manage administrative state of the vlan. + type: bool + mode: + description: + - Set vlan mode to classical ethernet or fabricpath. This is a valid option + for Nexus 5000, 6000 and 7000 series. + type: str + choices: + - ce + - fabricpath + mapped_vni: + description: + - The Virtual Network Identifier (VNI) ID that is mapped to the VLAN. + type: int + state: + description: + - The state of the configuration after module completion. + - The state I(overridden) would override the configuration of all the + VLANs on the device (including VLAN 1) with the provided configuration in + the task. Use caution with this state. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged + +""" +EXAMPLES = """ +# Using merged + +# Before state: +# ------------- +# vlan 1 + +- name: Merge provided configuration with device configuration. + cisco.nxos.nxos_vlans: + config: + - vlan_id: 5 + name: test-vlan5 + - vlan_id: 10 + enabled: false + state: merged + +# After state: +# ------------ +# vlan 5 +# name test-vlan5 +# state active +# no shutdown +# vlan 10 +# state active +# shutdown + + +# Using replaced + +# Before state: +# ------------- +# vlan 1 +# vlan 5 +# name test-vlan5 +# vlan 10 +# shutdown + +- name: Replace device configuration of specified vlan with provided configuration. + cisco.nxos.nxos_vlans: + config: + - vlan_id: 5 + name: test-vlan + enabled: false + - vlan_id: 10 + enabled: false + state: replaced + +# After state: +# ------------ +# vlan 1 +# vlan 5 +# name test-vlan +# state active +# shutdown +# vlan 10 +# state active +# shutdown + + +# Using overridden + +# Before state: +# ------------- +# vlan 1 +# vlan 3 +# name testing +# vlan 5 +# name test-vlan5 +# shutdown +# vlan 10 +# shutdown + +- name: Override device configuration of all vlans with provided configuration. + cisco.nxos.nxos_vlans: + config: + - vlan_id: 5 + name: test-vlan + - vlan_id: 10 + state: active + state: overridden + +# After state: +# ------------ +# vlan 5 +# name test-vlan +# state active +# no shutdown +# vlan 10 +# state active +# no shutdown + + +# Using deleted + +# Before state: +# ------------- +# vlan 1 +# vlan 5 +# vlan 10 + +- name: Delete vlans. + cisco.nxos.nxos_vlans: + config: + - vlan_id: 5 + - vlan_id: 10 + state: deleted + +# After state: +# ------------ +# + +# Using rendered + +- name: Use rendered state to convert task input to device specific commands + cisco.nxos.nxos_vlans: + config: + - vlan_id: 5 + name: vlan5 + mapped_vni: 100 + + - vlan_id: 6 + name: vlan6 + state: suspend + state: rendered + +# Task Output (redacted) +# ----------------------- + +# rendered: +# - vlan 5 +# - name vlan5 +# - vn-segment 100 +# - vlan 6 +# - name vlan6 +# - state suspend + +# Using parsed + +# parsed.cfg +# ------------ +# { +# "TABLE_vlanbrief": { +# "ROW_vlanbrief": [ +# { +# "vlanshowbr-vlanid": "1", +# "vlanshowbr-vlanid-utf": "1", +# "vlanshowbr-vlanname": "default", +# "vlanshowbr-vlanstate": "active", +# "vlanshowbr-shutstate": "noshutdown" +# }, +# { +# "vlanshowbr-vlanid": "5", +# "vlanshowbr-vlanid-utf": "5", +# "vlanshowbr-vlanname": "vlan5", +# "vlanshowbr-vlanstate": "suspend", +# "vlanshowbr-shutstate": "noshutdown" +# }, +# { +# "vlanshowbr-vlanid": "6", +# "vlanshowbr-vlanid-utf": "6", +# "vlanshowbr-vlanname": "VLAN0006", +# "vlanshowbr-vlanstate": "active", +# "vlanshowbr-shutstate": "noshutdown" +# }, +# { +# "vlanshowbr-vlanid": "7", +# "vlanshowbr-vlanid-utf": "7", +# "vlanshowbr-vlanname": "vlan7", +# "vlanshowbr-vlanstate": "active", +# "vlanshowbr-shutstate": "noshutdown" +# } +# ] +# }, +# "TABLE_mtuinfo": { +# "ROW_mtuinfo": [ +# { +# "vlanshowinfo-vlanid": "1", +# "vlanshowinfo-media-type": "enet", +# "vlanshowinfo-vlanmode": "ce-vlan" +# }, +# { +# "vlanshowinfo-vlanid": "5", +# "vlanshowinfo-media-type": "enet", +# "vlanshowinfo-vlanmode": "ce-vlan" +# }, +# { +# "vlanshowinfo-vlanid": "6", +# "vlanshowinfo-media-type": "enet", +# "vlanshowinfo-vlanmode": "ce-vlan" +# }, +# { +# "vlanshowinfo-vlanid": "7", +# "vlanshowinfo-media-type": "enet", +# "vlanshowinfo-vlanmode": "ce-vlan" +# } +# ] +# } +# } +# +# vlan 1,5-7 +# vlan 5 +# state suspend +# name vlan5 +# vlan 7 +# name vlan7 +# vn-segment 100 + +- name: Use parsed state to convert externally supplied config to structured format + cisco.nxos.nxos_vlans: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task output (redacted) +# ----------------------- + +# parsed: +# - vlan_id: 5 +# enabled: True +# mode: "ce" +# name: "vlan5" +# state: suspend +# +# - vlan_id: 6 +# enabled: True +# mode: "ce" +# state: active +# +# - vlan_id: 7 +# enabled: True +# mode: "ce" +# name: "vlan7" +# state: active +# mapped_vni: 100 + +# Using gathered + +# Existing device config state +# ------------------------------- +# nxos-9k# show vlan | json +# {"TABLE_vlanbrief": {"ROW_vlanbrief": [{"vlanshowbr-vlanid": "1", "vlanshowbr-vlanid-utf": "1", "vlanshowbr-vlanname": "default", "vlanshowbr-vlanstate +# ": "active", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "5", "vlanshowbr-vlanid-utf": "5", "vlanshowbr-vlanname": "vlan5", "vlanshowb +# r-vlanstate": "suspend", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "6", "vlanshowbr-vlanid-utf": "6", "vlanshowbr-vlanname": "VLAN00 +# 06", "vlanshowbr-vlanstate": "active", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "7", "vlanshowbr-vlanid-utf": "7", "vlanshowbr-vlan +# name": "vlan7", "vlanshowbr-vlanstate": "active", "vlanshowbr-shutstate": "shutdown"}]}, "TABLE_mtuinfo": {"ROW_mtuinfo": [{"vlanshowinfo-vlanid": "1", +# "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "5", "vlanshowinfo-media-type": "enet", "vlanshowinfo- +# vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "6", "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "7" +# , "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}]}} +# +# nxos-9k# show running-config | section ^vlan +# vlan 1,5-7 +# vlan 5 +# state suspend +# name vlan5 +# vlan 7 +# shutdown +# name vlan7 +# vn-segment 190 + +- name: Gather vlans facts from the device using nxos_vlans + cisco.nxos.nxos_vlans: + state: gathered + +# Task output (redacted) +# ----------------------- +# gathered: +# - vlan_id: 5 +# enabled: True +# mode: "ce" +# name: "vlan5" +# state: suspend +# +# - vlan_id: 6 +# enabled: True +# mode: "ce" +# state: active +# +# - vlan_id: 7 +# enabled: False +# mode: "ce" +# name: "vlan7" +# state: active +# mapped_vni: 190 +""" +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: ['vlan 5', 'name test-vlan5', 'state suspend'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vlans.vlans import ( + VlansArgs, +) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.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/cisco/nxos/plugins/modules/nxos_vpc.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py new file mode 100644 index 00000000..f660efc1 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py @@ -0,0 +1,470 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vpc +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages global VPC configuration +description: +- Manages global VPC configuration +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- The feature vpc must be enabled before this module can be used +- If not using management vrf, vrf must be globally on the device before using in + the pkl config +- Although source IP isn't required on the command line it is required when using + this module. The PKL VRF must also be configured prior to using this module. +- Both pkl_src and pkl_dest are needed when changing PKL VRF. +options: + domain: + description: + - VPC domain + required: true + type: str + role_priority: + description: + - Role priority for device. Remember lower is better. + type: str + system_priority: + description: + - System priority device. Remember they must match between peers. + type: str + pkl_src: + description: + - Source IP address used for peer keepalive link + type: str + pkl_dest: + description: + - Destination (remote) IP address used for peer keepalive link + - pkl_dest is required whenever pkl options are used. + type: str + pkl_vrf: + description: + - VRF used for peer keepalive link + - The VRF must exist on the device before using pkl_vrf. + - "(Note) 'default' is an overloaded term: Default vrf context for pkl_vrf is + 'management'; 'pkl_vrf: default' refers to the literal 'default' rib." + type: str + peer_gw: + description: + - Enables/Disables peer gateway + type: bool + auto_recovery: + description: + - Enables/Disables auto recovery on platforms that support disable + - timers are not modifiable with this attribute + - mutually exclusive with auto_recovery_reload_delay + type: bool + auto_recovery_reload_delay: + description: + - Manages auto-recovery reload-delay timer in seconds + - mutually exclusive with auto_recovery + type: str + delay_restore: + description: + - manages delay restore command and config value in seconds + type: str + delay_restore_interface_vlan: + description: + - manages delay restore interface-vlan command and config value in seconds + - not supported on all platforms + type: str + delay_restore_orphan_port: + description: + - manages delay restore orphan-port command and config value in seconds + - not supported on all platforms + type: str + state: + description: + - Manages desired state of the resource + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- name: configure a simple asn + cisco.nxos.nxos_vpc: + domain: 100 + role_priority: 1000 + system_priority: 2000 + pkl_dest: 192.168.100.4 + pkl_src: 10.1.100.20 + peer_gw: true + auto_recovery: true + +- name: configure + cisco.nxos.nxos_vpc: + domain: 100 + role_priority: 32667 + system_priority: 2000 + peer_gw: true + pkl_src: 10.1.100.2 + pkl_dest: 192.168.100.4 + auto_recovery: true + +- name: Configure VPC with delay restore and existing keepalive VRF + cisco.nxos.nxos_vpc: + domain: 10 + role_priority: 28672 + system_priority: 2000 + delay_restore: 180 + peer_gw: true + pkl_src: 1.1.1.2 + pkl_dest: 1.1.1.1 + pkl_vrf: vpckeepalive + auto_recovery: true +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["vpc domain 100", + "peer-keepalive destination 192.168.100.4 source 10.1.100.20 vrf management", + "auto-recovery", "peer-gateway"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, + run_commands, +) + + +CONFIG_ARGS = { + "role_priority": "role priority {role_priority}", + "system_priority": "system-priority {system_priority}", + "delay_restore": "delay restore {delay_restore}", + "delay_restore_interface_vlan": "delay restore interface-vlan {delay_restore_interface_vlan}", + "delay_restore_orphan_port": "delay restore orphan-port {delay_restore_orphan_port}", + "peer_gw": "{peer_gw} peer-gateway", + "auto_recovery": "{auto_recovery} auto-recovery", + "auto_recovery_reload_delay": "auto-recovery reload-delay {auto_recovery_reload_delay}", +} + +PARAM_TO_DEFAULT_KEYMAP = { + "delay_restore": "60", + "delay_restore_interface_vlan": "10", + "delay_restore_orphan_port": "0", + "role_priority": "32667", + "system_priority": "32667", + "peer_gw": False, + "auto_recovery_reload_delay": 240, +} + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_vrf_list(module): + try: + body = run_commands(module, ["show vrf all | json"])[0] + vrf_table = body["TABLE_vrf"]["ROW_vrf"] + except (KeyError, AttributeError): + return [] + + vrf_list = [] + if vrf_table: + for each in vrf_table: + vrf_list.append(str(each["vrf_name"].lower())) + + return vrf_list + + +def get_auto_recovery_default(module): + auto = False + data = run_commands(module, ["show inventory | json"])[0] + pid = data["TABLE_inv"]["ROW_inv"][0]["productid"] + if re.search(r"N7K", pid): + auto = True + elif re.search(r"N9K", pid): + data = run_commands(module, ["show hardware | json"])[0] + ver = data["kickstart_ver_str"] + if re.search(r"7.0\(3\)F", ver): + auto = True + + return auto + + +def get_vpc(module): + body = run_commands(module, ["show vpc | json"])[0] + if body: + domain = str(body["vpc-domain-id"]) + else: + body = run_commands(module, ["show run vpc | inc domain"])[0] + if body: + domain = body.split()[2] + else: + domain = "not configured" + + vpc = {} + if domain != "not configured": + run = get_config(module, flags=["vpc all"]) + if run: + vpc["domain"] = domain + for key in PARAM_TO_DEFAULT_KEYMAP.keys(): + vpc[key] = PARAM_TO_DEFAULT_KEYMAP.get(key) + vpc["auto_recovery"] = get_auto_recovery_default(module) + vpc_list = run.split("\n") + for each in vpc_list: + if "role priority" in each: + line = each.split() + vpc["role_priority"] = line[-1] + if "system-priority" in each: + line = each.split() + vpc["system_priority"] = line[-1] + if re.search(r"delay restore \d+", each): + line = each.split() + vpc["delay_restore"] = line[-1] + if "delay restore interface-vlan" in each: + line = each.split() + vpc["delay_restore_interface_vlan"] = line[-1] + if "delay restore orphan-port" in each: + line = each.split() + vpc["delay_restore_orphan_port"] = line[-1] + if "auto-recovery" in each: + vpc["auto_recovery"] = False if "no " in each else True + line = each.split() + vpc["auto_recovery_reload_delay"] = line[-1] + if "peer-gateway" in each: + vpc["peer_gw"] = False if "no " in each else True + if "peer-keepalive destination" in each: + # destination is reqd; src & vrf are optional + m = re.search( + r"destination (?P<pkl_dest>[\d.]+)" + r"(?:.* source (?P<pkl_src>[\d.]+))*" + r"(?:.* vrf (?P<pkl_vrf>\S+))*", + each, + ) + if m: + for pkl in m.groupdict().keys(): + if m.group(pkl): + vpc[pkl] = m.group(pkl) + return vpc + + +def pkl_dependencies(module, delta, existing): + """peer-keepalive dependency checking. + 1. 'destination' is required with all pkl configs. + 2. If delta has optional pkl keywords present, then all optional pkl + keywords in existing must be added to delta, otherwise the device cli + will remove those values when the new config string is issued. + 3. The desired behavior for this set of properties is to merge changes; + therefore if an optional pkl property exists on the device but not + in the playbook, then that existing property should be retained. + Example: + CLI: peer-keepalive dest 10.1.1.1 source 10.1.1.2 vrf orange + Playbook: {pkl_dest: 10.1.1.1, pkl_vrf: blue} + Result: peer-keepalive dest 10.1.1.1 source 10.1.1.2 vrf blue + """ + pkl_existing = [i for i in existing.keys() if i.startswith("pkl")] + for pkl in pkl_existing: + param = module.params.get(pkl) + if not delta.get(pkl): + if param and param == existing[pkl]: + # delta is missing this param because it's idempotent; + # however another pkl command has changed; therefore + # explicitly add it to delta so that the cli retains it. + delta[pkl] = existing[pkl] + elif param is None and existing[pkl]: + # retain existing pkl commands even if not in playbook + delta[pkl] = existing[pkl] + + +def get_commands_to_config_vpc(module, vpc, domain, existing): + vpc = dict(vpc) + + domain_only = vpc.get("domain") + + commands = [] + if "pkl_dest" in vpc: + pkl_command = "peer-keepalive destination {pkl_dest}".format(**vpc) + if "pkl_src" in vpc: + pkl_command += " source {pkl_src}".format(**vpc) + if "pkl_vrf" in vpc: + pkl_command += " vrf {pkl_vrf}".format(**vpc) + commands.append(pkl_command) + + if "auto_recovery" in vpc: + if not vpc.get("auto_recovery"): + vpc["auto_recovery"] = "no" + else: + vpc["auto_recovery"] = "" + + if "peer_gw" in vpc: + if not vpc.get("peer_gw"): + vpc["peer_gw"] = "no" + else: + vpc["peer_gw"] = "" + + for param in vpc: + command = CONFIG_ARGS.get(param) + if command is not None: + command = command.format(**vpc).strip() + if "peer-gateway" in command: + commands.append("terminal dont-ask") + commands.append(command) + + if commands or domain_only: + commands.insert(0, "vpc domain {0}".format(domain)) + return commands + + +def main(): + argument_spec = dict( + domain=dict(required=True, type="str"), + role_priority=dict(required=False, type="str"), + system_priority=dict(required=False, type="str"), + pkl_src=dict(required=False), + pkl_dest=dict(required=False), + pkl_vrf=dict(required=False), + peer_gw=dict(required=False, type="bool"), + auto_recovery=dict(required=False, type="bool"), + auto_recovery_reload_delay=dict(required=False, type="str"), + delay_restore=dict(required=False, type="str"), + delay_restore_interface_vlan=dict(required=False, type="str"), + delay_restore_orphan_port=dict(required=False, type="str"), + state=dict(choices=["absent", "present"], default="present"), + ) + + mutually_exclusive = [("auto_recovery", "auto_recovery_reload_delay")] + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + warnings = list() + results = {"changed": False, "warnings": warnings} + + domain = module.params["domain"] + role_priority = module.params["role_priority"] + system_priority = module.params["system_priority"] + pkl_src = module.params["pkl_src"] + pkl_dest = module.params["pkl_dest"] + pkl_vrf = module.params["pkl_vrf"] + peer_gw = module.params["peer_gw"] + auto_recovery = module.params["auto_recovery"] + auto_recovery_reload_delay = module.params["auto_recovery_reload_delay"] + delay_restore = module.params["delay_restore"] + delay_restore_interface_vlan = module.params["delay_restore_interface_vlan"] + delay_restore_orphan_port = module.params["delay_restore_orphan_port"] + state = module.params["state"] + + args = dict( + domain=domain, + role_priority=role_priority, + system_priority=system_priority, + pkl_src=pkl_src, + pkl_dest=pkl_dest, + pkl_vrf=pkl_vrf, + peer_gw=peer_gw, + auto_recovery=auto_recovery, + auto_recovery_reload_delay=auto_recovery_reload_delay, + delay_restore=delay_restore, + delay_restore_interface_vlan=delay_restore_interface_vlan, + delay_restore_orphan_port=delay_restore_orphan_port, + ) + + if not pkl_dest: + if pkl_src: + module.fail_json(msg="dest IP for peer-keepalive is required" " when src IP is present") + elif pkl_vrf: + if pkl_vrf != "management": + module.fail_json( + msg="dest and src IP for peer-keepalive are required" " when vrf is present", + ) + else: + module.fail_json( + msg="dest IP for peer-keepalive is required" " when vrf is present", + ) + if pkl_vrf: + if pkl_vrf.lower() not in get_vrf_list(module): + module.fail_json( + msg="The VRF you are trying to use for the peer " + "keepalive link is not on device yet. Add it" + " first, please.", + ) + proposed = dict((k, v) for k, v in args.items() if v is not None) + existing = get_vpc(module) + + commands = [] + if state == "present": + delta = {} + for key, value in proposed.items(): + if str(value).lower() == "default" and key != "pkl_vrf": + # 'default' is a reserved word for vrf + value = PARAM_TO_DEFAULT_KEYMAP.get(key) + if existing.get(key) != value: + delta[key] = value + + if delta: + pkl_dependencies(module, delta, existing) + command = get_commands_to_config_vpc(module, delta, domain, existing) + commands.append(command) + elif state == "absent": + if existing: + if domain != existing["domain"]: + module.fail_json( + msg="You are trying to remove a domain that " "does not exist on the device", + ) + else: + commands.append("terminal dont-ask") + commands.append("no vpc domain {0}".format(domain)) + + cmds = flatten_list(commands) + results["commands"] = cmds + + if cmds: + results["changed"] = True + if not module.check_mode: + load_config(module, cmds) + if "configure" in cmds: + cmds.pop(0) + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py new file mode 100644 index 00000000..5e318457 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py @@ -0,0 +1,342 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vpc_interface +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages interface VPC configuration +description: +- Manages interface VPC configuration +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- Either vpc or peer_link param is required, but not both. +- C(state=absent) removes whatever VPC config is on a port-channel if one exists. +- Re-assigning a vpc or peerlink from one portchannel to another is not supported. The + module will force the user to unconfigure an existing vpc/pl before configuring + the same value on a new portchannel +options: + portchannel: + description: + - Group number of the portchannel that will be configured. + required: true + type: str + vpc: + description: + - VPC group/id that will be configured on associated portchannel. + type: str + peer_link: + description: + - Set to true/false for peer link config on associated portchannel. + type: bool + state: + description: + - Manages desired state of the resource. + choices: + - present + - absent + default: present + type: str +""" + +EXAMPLES = """ +- cisco.nxos.nxos_vpc_interface: + portchannel: 10 + vpc: 100 +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["interface port-channel100", "vpc 10"] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, + run_commands, +) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_portchannel_list(module): + portchannels = [] + pc_list = [] + + try: + body = run_commands(module, ["show port-channel summary | json"])[0] + pc_list = body["TABLE_channel"]["ROW_channel"] + except (KeyError, AttributeError, TypeError): + return portchannels + + if pc_list: + if isinstance(pc_list, dict): + pc_list = [pc_list] + + for pc in pc_list: + portchannels.append(pc["group"]) + + return portchannels + + +def get_existing_portchannel_to_vpc_mappings(module): + pc_vpc_mapping = {} + + try: + body = run_commands(module, ["show vpc brief | json"])[0] + vpc_table = body["TABLE_vpc"]["ROW_vpc"] + except (KeyError, AttributeError, TypeError): + vpc_table = None + + if vpc_table: + if isinstance(vpc_table, dict): + vpc_table = [vpc_table] + + for vpc in vpc_table: + pc_vpc_mapping[str(vpc["vpc-id"])] = str(vpc["vpc-ifindex"]) + + return pc_vpc_mapping + + +def peer_link_exists(module): + found = False + run = get_config(module, flags=["vpc"]) + + vpc_list = run.split("\n") + for each in vpc_list: + if "peer-link" in each: + found = True + return found + + +def get_active_vpc_peer_link(module): + peer_link = None + + try: + body = run_commands(module, ["show vpc brief | json"])[0] + peer_link = body["TABLE_peerlink"]["ROW_peerlink"]["peerlink-ifindex"] + except (KeyError, AttributeError, TypeError): + return peer_link + + return peer_link + + +def get_portchannel_vpc_config(module, portchannel): + peer_link_pc = None + peer_link = False + vpc = "" + pc = "" + config = {} + + try: + body = run_commands(module, ["show vpc brief | json"])[0] + table = body["TABLE_peerlink"]["ROW_peerlink"] + except (KeyError, AttributeError, TypeError): + table = {} + + if table: + peer_link_pc = table.get("peerlink-ifindex", None) + + if peer_link_pc: + plpc = str(peer_link_pc[2:]) + if portchannel == plpc: + config["portchannel"] = portchannel + config["peer-link"] = True + config["vpc"] = vpc + + mapping = get_existing_portchannel_to_vpc_mappings(module) + + for existing_vpc, port_channel in mapping.items(): + port_ch = str(port_channel[2:]) + if port_ch == portchannel: + pc = port_ch + vpc = str(existing_vpc) + + config["portchannel"] = pc + config["peer-link"] = peer_link + config["vpc"] = vpc + + return config + + +def get_commands_to_config_vpc_interface(portchannel, delta, config_value, existing): + commands = [] + + if not delta.get("peer-link") and existing.get("peer-link"): + commands.append("no vpc peer-link") + commands.insert(0, "interface port-channel{0}".format(portchannel)) + + elif delta.get("peer-link") and not existing.get("peer-link"): + commands.append("vpc peer-link") + commands.insert(0, "interface port-channel{0}".format(portchannel)) + + elif delta.get("vpc") and not existing.get("vpc"): + command = "vpc {0}".format(config_value) + commands.append(command) + commands.insert(0, "interface port-channel{0}".format(portchannel)) + + return commands + + +def state_present(portchannel, delta, config_value, existing): + commands = [] + + command = get_commands_to_config_vpc_interface(portchannel, delta, config_value, existing) + commands.append(command) + + return commands + + +def state_absent(portchannel, existing): + commands = [] + if existing.get("vpc"): + command = "no vpc" + commands.append(command) + elif existing.get("peer-link"): + command = "no vpc peer-link" + commands.append(command) + if commands: + commands.insert(0, "interface port-channel{0}".format(portchannel)) + + return commands + + +def main(): + argument_spec = dict( + portchannel=dict(required=True, type="str"), + vpc=dict(required=False, type="str"), + peer_link=dict(required=False, type="bool"), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[["vpc", "peer_link"]], + supports_check_mode=True, + ) + + warnings = list() + commands = [] + results = {"changed": False, "warnings": warnings} + + portchannel = module.params["portchannel"] + vpc = module.params["vpc"] + peer_link = module.params["peer_link"] + state = module.params["state"] + + args = {"portchannel": portchannel, "vpc": vpc, "peer-link": peer_link} + active_peer_link = None + + if portchannel not in get_portchannel_list(module): + if not portchannel.isdigit() or int(portchannel) not in get_portchannel_list(module): + module.fail_json( + msg="The portchannel you are trying to make a" + " VPC or PL is not created yet. " + "Create it first!", + ) + if vpc: + mapping = get_existing_portchannel_to_vpc_mappings(module) + + if vpc in mapping and portchannel != mapping[vpc].strip("Po"): + module.fail_json( + msg="This vpc is already configured on " + "another portchannel. Remove it first " + "before trying to assign it here. ", + existing_portchannel=mapping[vpc], + ) + + for vpcid, existing_pc in mapping.items(): + if portchannel == existing_pc.strip("Po") and vpcid != vpc: + module.fail_json( + msg="This portchannel already has another" + " VPC configured. Remove it first " + "before assigning this one", + existing_vpc=vpcid, + ) + + if peer_link_exists(module): + active_peer_link = get_active_vpc_peer_link(module) + if active_peer_link[-2:] == portchannel: + module.fail_json( + msg="That port channel is the current " + "PEER LINK. Remove it if you want it" + " to be a VPC", + ) + config_value = vpc + + elif peer_link is not None: + if peer_link_exists(module): + active_peer_link = get_active_vpc_peer_link(module)[2::] + if active_peer_link != portchannel: + if peer_link: + module.fail_json( + msg="A peer link already exists on" " the device. Remove it first", + current_peer_link="Po{0}".format(active_peer_link), + ) + config_value = "peer-link" + + proposed = dict((k, v) for k, v in args.items() if v is not None) + existing = get_portchannel_vpc_config(module, portchannel) + + if state == "present": + delta = dict(set(proposed.items()).difference(existing.items())) + if delta: + commands = state_present(portchannel, delta, config_value, existing) + + elif state == "absent" and existing: + commands = state_absent(portchannel, existing) + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + load_config(module, cmds) + results["changed"] = True + if "configure" in cmds: + cmds.pop(0) + + results["commands"] = cmds + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py new file mode 100644 index 00000000..66fa43ab --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py @@ -0,0 +1,616 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vrf +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages global VRF configuration. +description: +- This module provides declarative management of VRFs on CISCO NXOS network devices. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +- Trishna Guha (@trishnaguha) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- Cisco NX-OS creates the default VRF by itself. Therefore, you're not allowed to + use default as I(vrf) name in this module. +- C(vrf) name must be shorter than 32 chars. +- VRF names are not case sensible in NX-OS. Anyway, the name is stored just like it's + inserted by the user and it'll not be changed again unless the VRF is removed and + re-created. i.e. C(vrf=NTC) will create a VRF named NTC, but running it again with + C(vrf=ntc) will not cause a configuration change. +options: + name: + description: + - Name of VRF to be managed. + aliases: + - vrf + type: str + admin_state: + description: + - Administrative state of the VRF. + default: up + choices: + - up + - down + type: str + vni: + description: + - Specify virtual network identifier. Valid values are Integer or keyword 'default'. + type: str + rd: + description: + - VPN Route Distinguisher (RD). Valid values are a string in one of the route-distinguisher + formats (ASN2:NN, ASN4:NN, or IPV4:NN); the keyword 'auto', or the keyword 'default'. + type: str + interfaces: + description: + - List of interfaces to check the VRF has been configured correctly or keyword + 'default'. + type: list + elements: str + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for given vrf + C(name) for associated interfaces. If the value in the C(associated_interfaces) + does not match with the operational state of vrf interfaces on device it will + result in failure. + type: list + elements: str + aggregate: + description: List of VRFs definitions. + type: list + elements: dict + suboptions: + name: + description: + - Name of VRF to be managed. + aliases: + - vrf + type: str + admin_state: + description: + - Administrative state of the VRF. + choices: + - up + - down + type: str + vni: + description: + - Specify virtual network identifier. Valid values are Integer or keyword 'default'. + type: str + rd: + description: + - VPN Route Distinguisher (RD). Valid values are a string in one of the route-distinguisher + formats (ASN2:NN, ASN4:NN, or IPV4:NN); the keyword 'auto', or the keyword 'default'. + type: str + interfaces: + description: + - List of interfaces to check the VRF has been configured correctly or keyword + 'default'. + type: list + elements: str + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for given vrf + C(name) for associated interfaces. If the value in the C(associated_interfaces) + does not match with the operational state of vrf interfaces on device it will + result in failure. + type: list + elements: str + state: + description: + - Manages desired state of the resource. + choices: + - present + - absent + type: str + description: + description: + - Description of the VRF or keyword 'default'. + type: str + delay: + description: + - Time in seconds to wait before checking for the operational state on remote + device. This wait is applicable for operational state arguments. + type: int + purge: + description: + - Purge VRFs not defined in the I(aggregate) parameter. + type: bool + default: no + state: + description: + - Manages desired state of the resource. + default: present + choices: + - present + - absent + type: str + description: + description: + - Description of the VRF or keyword 'default'. + type: str + delay: + description: + - Time in seconds to wait before checking for the operational state on remote + device. This wait is applicable for operational state arguments. + default: 10 + type: int +""" + +EXAMPLES = """ +- name: Ensure ntc VRF exists on switch + cisco.nxos.nxos_vrf: + name: ntc + description: testing + state: present + +- name: Aggregate definition of VRFs + cisco.nxos.nxos_vrf: + aggregate: + - {name: test1, description: Testing, admin_state: down} + - {name: test2, interfaces: Ethernet1/2} + +- name: Aggregate definitions of VRFs with Purge + cisco.nxos.nxos_vrf: + aggregate: + - {name: ntc1, description: purge test1} + - {name: ntc2, description: purge test2} + state: present + purge: yes + +- name: Delete VRFs exist on switch + cisco.nxos.nxos_vrf: + aggregate: + - {name: ntc1} + - {name: ntc2} + state: absent + +- name: Assign interfaces to VRF declaratively + cisco.nxos.nxos_vrf: + name: test1 + interfaces: + - Ethernet2/3 + - Ethernet2/5 + +- name: Check interfaces assigned to VRF + cisco.nxos.nxos_vrf: + name: test1 + associated_interfaces: + - Ethernet2/3 + - Ethernet2/5 + +- name: Ensure VRF is tagged with interface Ethernet2/5 only (Removes from Ethernet2/3) + cisco.nxos.nxos_vrf: + name: test1 + interfaces: + - Ethernet2/5 + +- name: Delete VRF + cisco.nxos.nxos_vrf: + name: ntc + state: absent +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: + - vrf context ntc + - no shutdown + - interface Ethernet1/2 + - no switchport + - vrf member test2 +""" + +import re +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_default_spec, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_interface_type, + load_config, + run_commands, +) + + +def search_obj_in_list(name, lst): + for o in lst: + if o["name"] == name: + return o + + +def execute_show_command(command, module): + if "show run" not in command: + output = "json" + else: + output = "text" + cmds = [{"command": command, "output": output}] + body = run_commands(module, cmds) + return body + + +def get_existing_vrfs(module): + objs = list() + command = "show vrf all" + try: + body = execute_show_command(command, module)[0] + except IndexError: + return list() + try: + vrf_table = body["TABLE_vrf"]["ROW_vrf"] + except (TypeError, IndexError, KeyError): + return list() + + if isinstance(vrf_table, list): + for vrf in vrf_table: + obj = {} + obj["name"] = vrf["vrf_name"] + objs.append(obj) + + elif isinstance(vrf_table, dict): + obj = {} + obj["name"] = vrf_table["vrf_name"] + objs.append(obj) + + return objs + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + state = module.params["state"] + purge = module.params["purge"] + + args = ("rd", "description", "vni") + + for w in want: + name = w["name"] + admin_state = w["admin_state"] + vni = w["vni"] + interfaces = w.get("interfaces") or [] + if purge: + state = "absent" + else: + state = w["state"] + del w["state"] + + obj_in_have = search_obj_in_list(name, have) + if state == "absent" and obj_in_have: + commands.append("no vrf context {0}".format(name)) + + elif state == "present": + if not obj_in_have: + commands.append("vrf context {0}".format(name)) + for item in args: + candidate = w.get(item) + if candidate and candidate != "default": + cmd = item + " " + str(candidate) + commands.append(cmd) + if admin_state == "up": + commands.append("no shutdown") + elif admin_state == "down": + commands.append("shutdown") + commands.append("exit") + + if interfaces and interfaces[0] != "default": + for i in interfaces: + commands.append("interface {0}".format(i)) + if get_interface_type(i) in ( + "ethernet", + "portchannel", + ): + commands.append("no switchport") + commands.append("vrf member {0}".format(name)) + + else: + # If vni is already configured on vrf, unconfigure it first. + if vni: + if obj_in_have.get("vni") and vni != obj_in_have.get("vni"): + commands.append("no vni {0}".format(obj_in_have.get("vni"))) + + for item in args: + candidate = w.get(item) + if candidate == "default": + if obj_in_have.get(item): + cmd = "no " + item + " " + obj_in_have.get(item) + commands.append(cmd) + elif candidate and candidate != obj_in_have.get(item): + cmd = item + " " + str(candidate) + commands.append(cmd) + if admin_state and admin_state != obj_in_have.get("admin_state"): + if admin_state == "up": + commands.append("no shutdown") + elif admin_state == "down": + commands.append("shutdown") + + if commands: + commands.insert(0, "vrf context {0}".format(name)) + commands.append("exit") + + if interfaces and interfaces[0] != "default": + if not obj_in_have["interfaces"]: + for i in interfaces: + commands.append("vrf context {0}".format(name)) + commands.append("exit") + commands.append("interface {0}".format(i)) + if get_interface_type(i) in ( + "ethernet", + "portchannel", + ): + commands.append("no switchport") + commands.append("vrf member {0}".format(name)) + + elif set(interfaces) != set(obj_in_have["interfaces"]): + missing_interfaces = list(set(interfaces) - set(obj_in_have["interfaces"])) + for i in missing_interfaces: + commands.append("vrf context {0}".format(name)) + commands.append("exit") + commands.append("interface {0}".format(i)) + if get_interface_type(i) in ( + "ethernet", + "portchannel", + ): + commands.append("no switchport") + commands.append("vrf member {0}".format(name)) + + superfluous_interfaces = list( + set(obj_in_have["interfaces"]) - set(interfaces), + ) + for i in superfluous_interfaces: + commands.append("vrf context {0}".format(name)) + commands.append("exit") + commands.append("interface {0}".format(i)) + if get_interface_type(i) in ( + "ethernet", + "portchannel", + ): + commands.append("no switchport") + commands.append("no vrf member {0}".format(name)) + elif interfaces and interfaces[0] == "default": + if obj_in_have["interfaces"]: + for i in obj_in_have["interfaces"]: + commands.append("vrf context {0}".format(name)) + commands.append("exit") + commands.append("interface {0}".format(i)) + if get_interface_type(i) in ( + "ethernet", + "portchannel", + ): + commands.append("no switchport") + commands.append("no vrf member {0}".format(name)) + + if purge: + existing = get_existing_vrfs(module) + if existing: + for h in existing: + if h["name"] in ("default", "management"): + pass + else: + obj_in_want = search_obj_in_list(h["name"], want) + if not obj_in_want: + commands.append("no vrf context {0}".format(h["name"])) + + return commands + + +def validate_vrf(name, module): + if name: + name = name.strip() + if name == "default": + module.fail_json(msg="cannot use default as name of a VRF") + elif len(name) > 32: + module.fail_json(msg="VRF name exceeded max length of 32", name=name) + else: + return name + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get("aggregate") + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + d["name"] = validate_vrf(d["name"], module) + obj.append(d) + else: + obj.append( + { + "name": validate_vrf(module.params["name"], module), + "description": module.params["description"], + "vni": module.params["vni"], + "rd": module.params["rd"], + "admin_state": module.params["admin_state"], + "state": module.params["state"], + "interfaces": module.params["interfaces"], + "associated_interfaces": module.params["associated_interfaces"], + }, + ) + return obj + + +def get_value(arg, config, module): + extra_arg_regex = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(arg), re.M) + value = "" + if arg in config: + value = extra_arg_regex.search(config).group("value") + return value + + +def map_config_to_obj(want, element_spec, module): + objs = list() + + for w in want: + obj = deepcopy(element_spec) + del obj["delay"] + del obj["state"] + + command = "show vrf {0}".format(w["name"]) + try: + body = execute_show_command(command, module)[0] + vrf_table = body["TABLE_vrf"]["ROW_vrf"] + except (TypeError, IndexError): + return list() + + name = vrf_table["vrf_name"] + obj["name"] = name + obj["admin_state"] = vrf_table["vrf_state"].lower() + + command = "show run all | section vrf.context.{0}".format(name) + body = execute_show_command(command, module)[0] + extra_params = ["vni", "rd", "description"] + for param in extra_params: + obj[param] = get_value(param, body, module) + + obj["interfaces"] = [] + command = "show vrf {0} interface".format(name) + try: + body = execute_show_command(command, module)[0] + vrf_int = body["TABLE_if"]["ROW_if"] + except (TypeError, IndexError): + vrf_int = None + + if vrf_int: + if isinstance(vrf_int, list): + for i in vrf_int: + intf = i["if_name"] + obj["interfaces"].append(intf) + elif isinstance(vrf_int, dict): + intf = vrf_int["if_name"] + obj["interfaces"].append(intf) + + objs.append(obj) + return objs + + +def check_declarative_intent_params(want, module, element_spec, result): + have = None + is_delay = False + + for w in want: + if w.get("associated_interfaces") is None: + continue + + if result["changed"] and not is_delay: + time.sleep(module.params["delay"]) + is_delay = True + + if have is None: + have = map_config_to_obj(want, element_spec, module) + + for i in w["associated_interfaces"]: + obj_in_have = search_obj_in_list(w["name"], have) + + if obj_in_have: + interfaces = obj_in_have.get("interfaces") + if interfaces is not None and i not in interfaces: + module.fail_json(msg="Interface %s not configured on vrf %s" % (i, w["name"])) + + +def vrf_error_check(module, commands, responses): + """Checks for VRF config errors and executes a retry in some circumstances.""" + pattern = "ERROR: Deletion of VRF .* in progress" + if re.search(pattern, str(responses)): + # Allow delay/retry for VRF changes + time.sleep(15) + responses = load_config(module, commands, opts={"catch_clierror": True}) + if re.search(pattern, str(responses)): + module.fail_json(msg="VRF config (and retry) failure: %s " % responses) + module.warn("VRF config delayed by VRF deletion - passed on retry") + + +def main(): + """main entry point for module execution""" + element_spec = dict( + name=dict(type="str", aliases=["vrf"]), + description=dict(type="str"), + vni=dict(type="str"), + rd=dict(type="str"), + admin_state=dict(type="str", default="up", choices=["up", "down"]), + interfaces=dict(type="list", elements="str"), + associated_interfaces=dict(type="list", elements="str"), + delay=dict(type="int", default=10), + state=dict(type="str", default="present", choices=["present", "absent"]), + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type="list", elements="dict", options=aggregate_spec), + purge=dict(type="bool", default=False), + ) + + argument_spec.update(element_spec) + + required_one_of = [["name", "aggregate"]] + mutually_exclusive = [["name", "aggregate"]] + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + warnings = list() + result = {"changed": False} + if warnings: + result["warnings"] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(want, element_spec, module) + + commands = map_obj_to_commands((want, have), module) + result["commands"] = commands + + if commands and not module.check_mode: + responses = load_config(module, commands, opts={"catch_clierror": True}) + vrf_error_check(module, commands, responses) + result["changed"] = True + + check_declarative_intent_params(want, module, element_spec, result) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py new file mode 100644 index 00000000..bf155ce8 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py @@ -0,0 +1,274 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vrf_af +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages VRF AF. +description: +- Manages VRF AF +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- Default, where supported, restores params default value. +- In case of C(state=absent) the address-family configuration will be absent. Therefore + the options C(route_target_both_auto_evpn) and C(route_targets) are ignored. +options: + vrf: + description: + - Name of the VRF. + required: true + type: str + afi: + description: + - Address-Family Identifier (AFI). + required: true + choices: + - ipv4 + - ipv6 + type: str + route_target_both_auto_evpn: + description: + - Enable/Disable the EVPN route-target 'auto' setting for both import and export + target communities. + type: bool + route_targets: + description: + - Specify the route-targets which should be imported and/or exported under the + AF. This argument accepts a list of dicts that specify the route-target, the + direction (import|export|both) and state of each route-target. Default direction + is C(direction=both). See examples. + suboptions: + rt: + description: + - Defines the route-target itself + required: true + type: str + direction: + description: + - Indicates the direction of the route-target (import|export|both) + choices: + - import + - export + - both + default: both + type: str + state: + description: + - Determines whether the route-target with the given direction should be present + or not on the device. + choices: + - present + - absent + default: present + type: str + elements: dict + type: list + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- cisco.nxos.nxos_vrf_af: + vrf: ntc + afi: ipv4 + route_target_both_auto_evpn: true + state: present + +- cisco.nxos.nxos_vrf_af: + vrf: ntc + afi: ipv4 + route_targets: + - rt: 65000:1000 + direction: import + - rt: 65001:1000 + direction: import + +- cisco.nxos.nxos_vrf_af: + vrf: ntc + afi: ipv4 + route_targets: + - rt: 65000:1000 + direction: import + - rt: 65001:1000 + state: absent + +- cisco.nxos.nxos_vrf_af: + vrf: ntc + afi: ipv4 + route_targets: + - rt: 65000:1000 + direction: export + - rt: 65001:1000 + direction: export + +- cisco.nxos.nxos_vrf_af: + vrf: ntc + afi: ipv4 + route_targets: + - rt: 65000:1000 + direction: export + state: absent + +- cisco.nxos.nxos_vrf_af: + vrf: ntc + afi: ipv4 + route_targets: + - rt: 65000:1000 + direction: both + state: present + - rt: 65001:1000 + direction: import + state: present + - rt: 65002:1000 + direction: both + state: absent +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["vrf context ntc", "address-family ipv4 unicast"] +""" +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + get_config, + load_config, +) + + +def match_current_rt(rt, direction, current, rt_commands): + command = "route-target %s %s" % (direction, rt.get("rt")) + match = re.findall(command, current, re.M) + want = bool(rt.get("state") != "absent") + if not match and want: + rt_commands.append(command) + elif match and not want: + rt_commands.append("no %s" % command) + return rt_commands + + +def main(): + argument_spec = dict( + vrf=dict(required=True), + afi=dict(required=True, choices=["ipv4", "ipv6"]), + route_target_both_auto_evpn=dict(required=False, type="bool"), + state=dict(choices=["present", "absent"], default="present"), + route_targets=dict( + type="list", + elements="dict", + options=dict( + rt=dict(type="str", required=True), + direction=dict(choices=["import", "export", "both"], default="both"), + state=dict(choices=["present", "absent"], default="present"), + ), + ), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + result = {"changed": False, "warnings": warnings} + + config_text = get_config(module) + config = NetworkConfig(indent=2, contents=config_text) + + path = [ + "vrf context %s" % module.params["vrf"], + "address-family %s unicast" % module.params["afi"], + ] + + try: + current = config.get_block_config(path) + except ValueError: + current = None + + commands = list() + if current and module.params["state"] == "absent": + commands.append("no address-family %s unicast" % module.params["afi"]) + + elif module.params["state"] == "present": + rt_commands = list() + + if not current: + commands.append("address-family %s unicast" % module.params["afi"]) + current = "" + + have_auto_evpn = "route-target both auto evpn" in current + if module.params["route_target_both_auto_evpn"] is not None: + want_auto_evpn = bool(module.params["route_target_both_auto_evpn"]) + if want_auto_evpn and not have_auto_evpn: + commands.append("route-target both auto evpn") + elif have_auto_evpn and not want_auto_evpn: + commands.append("no route-target both auto evpn") + + if module.params["route_targets"] is not None: + for rt in module.params["route_targets"]: + if rt.get("direction") == "both" or not rt.get("direction"): + platform = get_capabilities(module)["device_info"]["network_os_platform"] + if platform.startswith("N9K") and rt.get("rt") == "auto": + rt_commands = match_current_rt(rt, "both", current, rt_commands) + else: + rt_commands = match_current_rt(rt, "import", current, rt_commands) + rt_commands = match_current_rt(rt, "export", current, rt_commands) + else: + rt_commands = match_current_rt(rt, rt.get("direction"), current, rt_commands) + + if rt_commands: + commands.extend(rt_commands) + + if commands and current: + commands.insert(0, "address-family %s unicast" % module.params["afi"]) + + if commands: + commands.insert(0, "vrf context %s" % module.params["vrf"]) + if not module.check_mode: + load_config(module, commands) + result["changed"] = True + + result["commands"] = commands + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py new file mode 100644 index 00000000..9e9a0cef --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vrf_interface +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages interface specific VRF configuration. +description: +- Manages interface specific VRF configuration. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- VRF needs to be added globally with M(cisco.nxos.nxos_vrf) before adding a VRF to an interface. +- Remove a VRF from an interface will still remove all L3 attributes just as it does + from CLI. +- VRF is not read from an interface until IP address is configured on that interface. +options: + vrf: + description: + - Name of VRF to be managed. + required: true + type: str + interface: + description: + - Full name of interface to be managed, i.e. Ethernet1/1. + required: true + type: str + state: + description: + - Manages desired state of the resource. + required: false + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- name: Ensure vrf ntc exists on Eth1/1 + cisco.nxos.nxos_vrf_interface: + vrf: ntc + interface: Ethernet1/1 + state: present + +- name: Ensure ntc VRF does not exist on Eth1/1 + cisco.nxos.nxos_vrf_interface: + vrf: ntc + interface: Ethernet1/1 + state: absent +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["interface loopback16", "vrf member ntc"] +""" +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + get_interface_type, + load_config, + normalize_interface, + run_commands, +) + + +def execute_show_command(command, module): + if "show run" not in command: + output = "json" + else: + output = "text" + cmds = [{"command": command, "output": output}] + return run_commands(module, cmds)[0] + + +def get_interface_mode(interface, intf_type, module): + command = "show interface {0}".format(interface) + interface = {} + mode = "unknown" + + if intf_type in ["ethernet", "portchannel"]: + body = execute_show_command(command, module) + try: + interface_table = body["TABLE_interface"]["ROW_interface"] + except KeyError: + return mode + + if interface_table and isinstance(interface_table, dict): + mode = str(interface_table.get("eth_mode", "layer3")) + if mode == "access" or mode == "trunk": + mode = "layer2" + else: + return mode + + elif intf_type == "loopback" or intf_type == "svi": + mode = "layer3" + return mode + + +def get_vrf_list(module): + command = "show vrf all" + vrf_list = [] + body = execute_show_command(command, module) + + try: + vrf_table = body["TABLE_vrf"]["ROW_vrf"] + except (KeyError, AttributeError): + return vrf_list + + for each in vrf_table: + vrf_list.append(str(each["vrf_name"])) + + return vrf_list + + +def get_interface_info(interface, module): + if not interface.startswith("loopback"): + interface = interface.capitalize() + + command = "show run interface {0}".format(interface) + vrf_regex = r".*vrf\s+member\s+(?P<vrf>\S+).*" + + try: + body = execute_show_command(command, module) + match_vrf = re.match(vrf_regex, body, re.DOTALL) + group_vrf = match_vrf.groupdict() + vrf = group_vrf["vrf"] + except (AttributeError, TypeError): + return "" + + return vrf + + +def is_default(interface, module): + command = "show run interface {0}".format(interface) + + try: + body = execute_show_command(command, module) + raw_list = body.split("\n") + if raw_list[-1].startswith("interface"): + return True + else: + return False + + except (KeyError, IndexError): + return "DNE" + + +def main(): + argument_spec = dict( + vrf=dict(required=True), + interface=dict(type="str", required=True), + state=dict(default="present", choices=["present", "absent"], required=False), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + vrf = module.params["vrf"] + interface = normalize_interface(module.params["interface"]) + state = module.params["state"] + + device_info = get_capabilities(module) + network_api = device_info.get("network_api", "nxapi") + + current_vrfs = get_vrf_list(module) + if vrf not in current_vrfs: + warnings.append("The VRF is not present/active on the device. Use nxos_vrf to fix this.") + + intf_type = get_interface_type(interface) + if intf_type != "ethernet" and network_api == "cliconf": + if is_default(interface, module) == "DNE": + module.fail_json( + msg="interface does not exist on switch. Verify " + "switch platform or create it first with " + "nxos_interfaces if it's a logical interface", + ) + + mode = get_interface_mode(interface, intf_type, module) + if mode == "layer2": + module.fail_json( + msg="Ensure interface is a Layer 3 port before " + "configuring a VRF on an interface. You can " + "use nxos_interfaces", + ) + + current_vrf = get_interface_info(interface, module) + existing = dict(interface=interface, vrf=current_vrf) + changed = False + + if not existing["vrf"]: + pass + elif vrf != existing["vrf"] and state == "absent": + module.fail_json( + msg="The VRF you are trying to remove " + "from the interface does not exist " + "on that interface.", + interface=interface, + proposed_vrf=vrf, + existing_vrf=existing["vrf"], + ) + + commands = [] + if existing: + if state == "absent": + if existing and vrf == existing["vrf"]: + command = "no vrf member {0}".format(vrf) + commands.append(command) + + elif state == "present": + if existing["vrf"] != vrf: + command = "vrf member {0}".format(vrf) + commands.append(command) + + if commands: + commands.insert(0, "interface {0}".format(interface)) + + if commands: + if module.check_mode: + module.exit_json(changed=True, commands=commands) + else: + load_config(module, commands) + changed = True + if "configure" in commands: + commands.pop(0) + + results["commands"] = commands + results["changed"] = changed + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py new file mode 100644 index 00000000..b5a026c4 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py @@ -0,0 +1,432 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vrrp +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages VRRP configuration on NX-OS switches. +description: +- Manages VRRP configuration on NX-OS switches. +version_added: 1.0.0 +author: +- Jason Edelman (@jedelman8) +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- VRRP feature needs to be enabled first on the system. +- SVIs must exist before using this module. +- Interface must be a L3 port before using this module. +- C(state=absent) removes the VRRP group if it exists on the device. +- VRRP cannot be configured on loopback interfaces. +options: + group: + description: + - VRRP group number. + required: true + type: str + interface: + description: + - Full name of interface that is being managed for VRRP. + required: true + type: str + interval: + description: + - Time interval between advertisement or 'default' keyword + required: false + type: str + priority: + description: + - VRRP priority or 'default' keyword + type: str + preempt: + description: + - Enable/Disable preempt. + type: bool + vip: + description: + - VRRP virtual IP address or 'default' keyword + type: str + authentication: + description: + - Clear text authentication string or 'default' keyword + type: str + admin_state: + description: + - Used to enable or disable the VRRP process. + choices: + - shutdown + - no shutdown + - default + default: shutdown + type: str + state: + description: + - Specify desired state of the resource. + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +- name: Ensure vrrp group 100 and vip 10.1.100.1 is on vlan10 + cisco.nxos.nxos_vrrp: + interface: vlan10 + group: 100 + vip: 10.1.100.1 + +- name: Ensure removal of the vrrp group config + cisco.nxos.nxos_vrrp: + interface: vlan10 + group: 100 + vip: 10.1.100.1 + state: absent + +- name: Re-config with more params + cisco.nxos.nxos_vrrp: + interface: vlan10 + group: 100 + vip: 10.1.100.1 + preempt: false + priority: 130 + authentication: AUTHKEY +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["interface vlan10", "vrrp 150", "address 10.1.15.1", + "authentication text testing", "no shutdown"] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + get_interface_type, + load_config, + run_commands, +) + + +PARAM_TO_DEFAULT_KEYMAP = { + "priority": "100", + "interval": "1", + "vip": "0.0.0.0", + "admin_state": "shutdown", +} + + +def execute_show_command(command, module): + if "show run" not in command: + output = "json" + else: + output = "text" + + commands = [{"command": command, "output": output}] + return run_commands(module, commands)[0] + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if new_key: + if value: + new_dict[new_key] = str(value) + else: + new_dict[new_key] = value + return new_dict + + +def is_default(interface, module): + command = "show run interface {0}".format(interface) + + try: + body = execute_show_command(command, module) + if "invalid" in body.lower(): + return "DNE" + else: + raw_list = body.split("\n") + if raw_list[-1].startswith("interface"): + return True + else: + return False + except KeyError: + return "DNE" + + +def get_interface_mode(interface, intf_type, module): + command = "show interface {0}".format(interface) + interface = {} + mode = "unknown" + body = execute_show_command(command, module) + interface_table = body["TABLE_interface"]["ROW_interface"] + name = interface_table.get("interface") + + if intf_type in ["ethernet", "portchannel"]: + mode = str(interface_table.get("eth_mode", "layer3")) + + if mode == "access" or mode == "trunk": + mode = "layer2" + elif intf_type == "svi": + mode = "layer3" + + return mode, name + + +def get_vrr_status(group, module, interface): + command = "show run all | section interface.{0}$".format(interface) + body = execute_show_command(command, module) + vrf_index = None + admin_state = "shutdown" + + if body: + splitted_body = body.splitlines() + for index in range(0, len(splitted_body) - 1): + if splitted_body[index].strip() == "vrrp {0}".format(group): + vrf_index = index + vrf_section = splitted_body[vrf_index::] + + for line in vrf_section: + if line.strip() == "no shutdown": + admin_state = "no shutdown" + break + + return admin_state + + +def get_existing_vrrp(interface, group, module, name): + command = "show vrrp detail interface {0}".format(interface) + body = execute_show_command(command, module) + vrrp = {} + + vrrp_key = { + "sh_group_id": "group", + "sh_vip_addr": "vip", + "sh_priority": "priority", + "sh_group_preempt": "preempt", + "sh_auth_text": "authentication", + "sh_adv_interval": "interval", + } + + try: + vrrp_table = body["TABLE_vrrp_group"] + except (AttributeError, IndexError, TypeError): + return {} + + if isinstance(vrrp_table, dict): + vrrp_table = [vrrp_table] + + for each_vrrp in vrrp_table: + vrrp_row = each_vrrp["ROW_vrrp_group"] + parsed_vrrp = apply_key_map(vrrp_key, vrrp_row) + + if parsed_vrrp["preempt"] == "Disable": + parsed_vrrp["preempt"] = False + elif parsed_vrrp["preempt"] == "Enable": + parsed_vrrp["preempt"] = True + + if parsed_vrrp["group"] == group: + parsed_vrrp["admin_state"] = get_vrr_status(group, module, name) + return parsed_vrrp + + return vrrp + + +def get_commands_config_vrrp(delta, existing, group): + commands = [] + + CMDS = { + "priority": "priority {0}", + "preempt": "preempt", + "vip": "address {0}", + "interval": "advertisement-interval {0}", + "auth": "authentication text {0}", + "admin_state": "{0}", + } + + for arg in ["vip", "priority", "interval", "admin_state"]: + val = delta.get(arg) + if val == "default": + val = PARAM_TO_DEFAULT_KEYMAP.get(arg) + if val != existing.get(arg): + commands.append((CMDS.get(arg)).format(val)) + elif val: + commands.append((CMDS.get(arg)).format(val)) + + preempt = delta.get("preempt") + auth = delta.get("authentication") + + if preempt: + commands.append(CMDS.get("preempt")) + elif preempt is False: + commands.append("no " + CMDS.get("preempt")) + if auth: + if auth != "default": + commands.append((CMDS.get("auth")).format(auth)) + elif existing.get("authentication"): + commands.append("no authentication") + + if commands: + commands.insert(0, "vrrp {0}".format(group)) + + return commands + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def validate_params(param, module): + value = module.params[param] + + if param == "group": + try: + if int(value) < 1 or int(value) > 255: + raise ValueError + except ValueError: + module.fail_json( + msg="Warning! 'group' must be an integer between" " 1 and 255", + group=value, + ) + elif param == "priority": + try: + if int(value) < 1 or int(value) > 254: + raise ValueError + except ValueError: + module.fail_json( + msg="Warning! 'priority' must be an integer " "between 1 and 254", + priority=value, + ) + + +def main(): + argument_spec = dict( + group=dict(required=True, type="str"), + interface=dict(required=True), + interval=dict(required=False, type="str"), + priority=dict(required=False, type="str"), + preempt=dict(required=False, type="bool"), + vip=dict(required=False, type="str"), + admin_state=dict( + required=False, + type="str", + choices=["shutdown", "no shutdown", "default"], + default="shutdown", + ), + authentication=dict(required=False, type="str", no_log=True), + state=dict(choices=["absent", "present"], required=False, default="present"), + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + results = {"changed": False, "commands": [], "warnings": warnings} + + state = module.params["state"] + interface = module.params["interface"].lower() + group = module.params["group"] + priority = module.params["priority"] + interval = module.params["interval"] + preempt = module.params["preempt"] + vip = module.params["vip"] + authentication = module.params["authentication"] + admin_state = module.params["admin_state"] + + device_info = get_capabilities(module) + network_api = device_info.get("network_api", "nxapi") + + if state == "present" and not vip: + module.fail_json(msg='the "vip" param is required when state=present') + + intf_type = get_interface_type(interface) + if intf_type != "ethernet" and network_api == "cliconf": + if is_default(interface, module) == "DNE": + module.fail_json( + msg="That interface does not exist yet. Create " "it first.", + interface=interface, + ) + if intf_type == "loopback": + module.fail_json( + msg="Loopback interfaces don't support VRRP.", + interface=interface, + ) + + mode, name = get_interface_mode(interface, intf_type, module) + if mode == "layer2": + module.fail_json( + msg="That interface is a layer2 port.\nMake it " "a layer 3 port first.", + interface=interface, + ) + + args = dict( + group=group, + priority=priority, + preempt=preempt, + vip=vip, + authentication=authentication, + interval=interval, + admin_state=admin_state, + ) + + proposed = dict((k, v) for k, v in args.items() if v is not None) + existing = get_existing_vrrp(interface, group, module, name) + + commands = [] + + if state == "present": + delta = dict(set(proposed.items()).difference(existing.items())) + if delta: + command = get_commands_config_vrrp(delta, existing, group) + if command: + commands.append(command) + elif state == "absent": + if existing: + commands.append(["no vrrp {0}".format(group)]) + + if commands: + commands.insert(0, ["interface {0}".format(interface)]) + commands = flatten_list(commands) + results["commands"] = commands + results["changed"] = True + if not module.check_mode: + load_config(module, commands) + if "configure" in commands: + commands.pop(0) + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py new file mode 100644 index 00000000..d95d95a9 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py @@ -0,0 +1,354 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or +# https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +DOCUMENTATION = """ +module: nxos_vsan +short_description: Configuration of vsan for Cisco NXOS MDS Switches. +description: +- Configuration of vsan for Cisco MDS NXOS. +version_added: 1.0.0 +author: +- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com) +notes: +- Tested against Cisco MDS NX-OS 8.4(1) +options: + vsan: + description: + - List of vsan details to be added or removed + type: list + elements: dict + suboptions: + id: + description: + - Vsan id + required: true + type: int + name: + description: + - Name of the vsan + type: str + suspend: + description: + - suspend the vsan if True + type: bool + remove: + description: + - Removes the vsan if True + type: bool + interface: + description: + - List of vsan's interfaces to be added + type: list + elements: str +""" + +EXAMPLES = """ +- name: Test that vsan module works + cisco.nxos.nxos_vsan: + vsan: + - id: 922 + interface: + - fc1/1 + - fc1/2 + - port-channel 1 + name: vsan-SAN-A + remove: false + suspend: false + - id: 923 + interface: + - fc1/11 + - fc1/21 + - port-channel 2 + name: vsan-SAN-B + remove: false + suspend: true + - id: 1923 + name: vsan-SAN-Old + remove: true +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: + - terminal dont-ask + - vsan database + - vsan 922 interface fc1/40 + - vsan 922 interface port-channel 155 + - no terminal dont-ask +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +__metaclass__ = type + + +class Vsan(object): + def __init__(self, vsanid): + self.vsanid = vsanid + self.vsanname = None + self.vsanstate = None + self.vsanoperstate = None + self.vsaninterfaces = [] + + +class GetVsanInfoFromSwitch(object): + """docstring for GetVsanInfoFromSwitch""" + + def __init__(self, module): + self.module = module + self.vsaninfo = {} + self.processShowVsan() + self.processShowVsanMembership() + + def execute_show_vsan_cmd(self): + output = execute_show_command("show vsan", self.module)[0] + return output + + def execute_show_vsan_mem_cmd(self): + output = execute_show_command("show vsan membership", self.module)[0] + return output + + def processShowVsan(self): + patv = r"^vsan\s+(\d+)\s+information" + patnamestate = "name:(.*)state:(.*)" + patoperstate = "operational state:(.*)" + + output = self.execute_show_vsan_cmd().split("\n") + for o in output: + z = re.match(patv, o.strip()) + if z: + v = z.group(1).strip() + self.vsaninfo[v] = Vsan(v) + + z1 = re.match(patnamestate, o.strip()) + if z1: + n = z1.group(1).strip() + s = z1.group(2).strip() + self.vsaninfo[v].vsanname = n + self.vsaninfo[v].vsanstate = s + + z2 = re.match(patoperstate, o.strip()) + if z2: + oper = z2.group(1).strip() + self.vsaninfo[v].vsanoperstate = oper + + # 4094/4079 vsan is always present + self.vsaninfo["4079"] = Vsan("4079") + self.vsaninfo["4094"] = Vsan("4094") + + def processShowVsanMembership(self): + patv = r"^vsan\s+(\d+).*" + output = self.execute_show_vsan_mem_cmd().split("\n") + memlist = [] + v = None + for o in output: + z = re.match(patv, o.strip()) + if z: + if v is not None: + self.vsaninfo[v].vsaninterfaces = memlist + memlist = [] + v = z.group(1) + if "interfaces" not in o: + llist = o.strip().split() + memlist = memlist + llist + self.vsaninfo[v].vsaninterfaces = memlist + + def getVsanInfoObjects(self): + return self.vsaninfo + + +def execute_show_command(command, module, command_type="cli_show"): + output = "text" + commands = [{"command": command, "output": output}] + return run_commands(module, commands) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def main(): + vsan_element_spec = dict( + id=dict(required=True, type="int"), + name=dict(type="str"), + remove=dict(type="bool"), + suspend=dict(type="bool"), + interface=dict(type="list", elements="str"), + ) + + argument_spec = dict(vsan=dict(type="list", elements="dict", options=vsan_element_spec)) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + warnings = list() + messages = list() + commands_executed = list() + result = {"changed": False} + + obj = GetVsanInfoFromSwitch(module) + dictSwVsanObjs = obj.getVsanInfoObjects() + + commands = [] + vsan_list = module.params["vsan"] + + for eachvsan in vsan_list: + vsanid = str(eachvsan["id"]) + vsanname = eachvsan["name"] + vsanremove = eachvsan["remove"] + vsansuspend = eachvsan["suspend"] + vsaninterface_list = eachvsan["interface"] + + if int(vsanid) < 1 or int(vsanid) >= 4095: + module.fail_json( + msg=vsanid + " - This is an invalid vsan. Supported vsan range is 1-4094", + ) + + if vsanid in dictSwVsanObjs.keys(): + sw_vsanid = vsanid + sw_vsanname = dictSwVsanObjs[vsanid].vsanname + sw_vsanstate = dictSwVsanObjs[vsanid].vsanstate + sw_vsaninterfaces = dictSwVsanObjs[vsanid].vsaninterfaces + else: + sw_vsanid = None + sw_vsanname = None + sw_vsanstate = None + sw_vsaninterfaces = [] + + if vsanremove: + # Negative case: + if vsanid == "4079" or vsanid == "4094": + messages.append(str(vsanid) + " is a reserved vsan, hence cannot be removed") + continue + if vsanid == sw_vsanid: + commands.append("no vsan " + str(vsanid)) + messages.append("deleting the vsan " + str(vsanid)) + else: + messages.append( + "There is no vsan " + + str(vsanid) + + " present in the switch. Hence there is nothing to delete", + ) + continue + else: + # Negative case: + if vsanid == "4079" or vsanid == "4094": + messages.append( + str(vsanid) + " is a reserved vsan, and always present on the switch", + ) + else: + if vsanid == sw_vsanid: + messages.append( + "There is already a vsan " + + str(vsanid) + + " present in the switch. Hence there is nothing to configure", + ) + else: + commands.append("vsan " + str(vsanid)) + messages.append("creating vsan " + str(vsanid)) + + if vsanname is not None: + # Negative case: + if vsanid == "4079" or vsanid == "4094": + messages.append(str(vsanid) + " is a reserved vsan, and cannot be renamed") + else: + if vsanname == sw_vsanname: + messages.append( + "There is already a vsan " + + str(vsanid) + + " present in the switch, which has the name " + + vsanname + + " Hence there is nothing to configure", + ) + else: + commands.append("vsan " + str(vsanid) + " name " + vsanname) + messages.append("setting vsan name to " + vsanname + " for vsan " + str(vsanid)) + + if vsansuspend: + # Negative case: + if vsanid == "4079" or vsanid == "4094": + messages.append(str(vsanid) + " is a reserved vsan, and cannot be suspended") + else: + if sw_vsanstate == "suspended": + messages.append( + "There is already a vsan " + + str(vsanid) + + " present in the switch, which is in suspended state ", + ) + else: + commands.append("vsan " + str(vsanid) + " suspend") + messages.append("suspending the vsan " + str(vsanid)) + else: + if sw_vsanstate == "active": + messages.append( + "There is already a vsan " + + str(vsanid) + + " present in the switch, which is in active state ", + ) + else: + commands.append("no vsan " + str(vsanid) + " suspend") + messages.append("no suspending the vsan " + str(vsanid)) + + if vsaninterface_list is not None: + for each_interface_name in vsaninterface_list: + # For fcip,port-channel,vfc-port-channel need to remove the + # extra space to compare + temp = re.sub(" +", "", each_interface_name) + if temp in sw_vsaninterfaces: + messages.append( + each_interface_name + + " is already present in the vsan " + + str(vsanid) + + " interface list", + ) + else: + commands.append("vsan " + str(vsanid) + " interface " + each_interface_name) + messages.append( + "adding interface " + each_interface_name + " to vsan " + str(vsanid), + ) + + if len(commands) != 0: + commands = ["terminal dont-ask"] + ["vsan database"] + commands + ["no terminal dont-ask"] + + cmds = flatten_list(commands) + commands_executed = cmds + + if commands_executed: + if module.check_mode: + module.exit_json( + changed=False, + commands=commands_executed, + msg="Check Mode: No cmds issued to the hosts", + ) + else: + result["changed"] = True + load_config(module, commands_executed) + + result["messages"] = messages + result["commands"] = commands_executed + result["warnings"] = warnings + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py new file mode 100644 index 00000000..d1e3c39f --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py @@ -0,0 +1,215 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vtp_domain +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages VTP domain configuration. +description: +- Manages VTP domain configuration. +version_added: 1.0.0 +author: +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- VTP feature must be active on the device to use this module. +- This module is used to manage only VTP domain names. +- VTP domain names are case-sensible. +- If it's never been configured before, VTP version is set to 1 by default. Otherwise, + it leaves the previous configured version untouched. Use M(cisco.nxos.nxos_vtp_version) + to change it. +- Use this in combination with M(cisco.nxos.nxos_vtp_password) and M(cisco.nxos.nxos_vtp_version) + to fully manage VTP operations. +options: + domain: + description: + - VTP domain name. + required: true + type: str +""" + +EXAMPLES = """ +# ENSURE VTP DOMAIN IS CONFIGURED +- cisco.nxos.nxos_vtp_domain: + domain: ntc + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +""" + + +RETURN = """ +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"domain": "ntc"} +existing: + description: + - k/v pairs of existing vtp domain + returned: always + type: dict + sample: {"domain": "testing", "version": "2", "vtp_password": "password"} +end_state: + description: k/v pairs of vtp domain after module execution + returned: always + type: dict + sample: {"domain": "ntc", "version": "2", "vtp_password": "password"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["vtp domain ntc"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + load_config, + run_commands, +) + + +def execute_show_command(command, module, output="json"): + cmds = [{"command": command, "output": output}] + body = run_commands(module, cmds) + return body + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_vtp_config(module): + command = "show vtp status" + body = execute_show_command(command, module, "text")[0] + vtp_parsed = {} + + if body: + version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*" + domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*" + + try: + match_version = re.match(version_regex, body, re.DOTALL) + version = match_version.groupdict()["version"] + except AttributeError: + version = "" + + try: + match_domain = re.match(domain_regex, body, re.DOTALL) + domain = match_domain.groupdict()["domain"] + except AttributeError: + domain = "" + + if domain and version: + vtp_parsed["domain"] = domain + vtp_parsed["version"] = version + vtp_parsed["vtp_password"] = get_vtp_password(module) + + return vtp_parsed + + +def get_vtp_password(module): + command = "show vtp password" + output = "json" + cap = get_capabilities(module)["device_info"]["network_os_model"] + if re.search(r"Nexus 6", cap): + output = "text" + + body = execute_show_command(command, module, output)[0] + + if output == "json": + password = body.get("passwd", "") + else: + password = "" + rp = r"VTP Password: (\S+)" + mo = re.search(rp, body) + if mo: + password = mo.group(1) + + return str(password) + + +def main(): + argument_spec = dict(domain=dict(type="str", required=True)) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + domain = module.params["domain"] + + existing = get_vtp_config(module) + end_state = existing + + args = dict(domain=domain) + + changed = False + proposed = dict((k, v) for k, v in args.items() if v is not None) + delta = dict(set(proposed.items()).difference(existing.items())) + + commands = [] + if delta: + commands.append(["vtp domain {0}".format(domain)]) + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + changed = True + load_config(module, cmds) + end_state = get_vtp_config(module) + if "configure" in cmds: + cmds.pop(0) + + results = {} + results["proposed"] = proposed + results["existing"] = existing + results["end_state"] = end_state + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py new file mode 100644 index 00000000..c6e9cabd --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py @@ -0,0 +1,277 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vtp_password +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages VTP password configuration. +description: +- Manages VTP password configuration. +version_added: 1.0.0 +author: +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- VTP feature must be active on the device to use this module. +- This module is used to manage only VTP passwords. +- Use this in combination with M(cisco.nxos.nxos_vtp_domain) and M(cisco.nxos.nxos_vtp_version) to fully + manage VTP operations. +- You can set/remove password only if a VTP domain already exist. +- If C(state=absent) and no C(vtp_password) is provided, it remove the current VTP + password. +- If C(state=absent) and C(vtp_password) is provided, the proposed C(vtp_password) + has to match the existing one in order to remove it. +options: + vtp_password: + description: + - VTP password + type: str + state: + description: + - Manage the state of the resource + default: present + choices: + - present + - absent + type: str +""" + +EXAMPLES = """ +# ENSURE VTP PASSWORD IS SET +- cisco.nxos.nxos_vtp_password: + state: present + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' + +# ENSURE VTP PASSWORD IS REMOVED +- cisco.nxos.nxos_vtp_password: + state: absent + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +""" + +RETURN = """ +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"vtp_password": "new_ntc"} +existing: + description: + - k/v pairs of existing vtp + returned: always + type: dict + sample: {"domain": "ntc", "version": "1", "vtp_password": "ntc"} +end_state: + description: k/v pairs of vtp after module execution + returned: always + type: dict + sample: {"domain": "ntc", "version": "1", "vtp_password": "new_ntc"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["vtp password new_ntc"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + load_config, + run_commands, +) + + +def execute_show_command(command, module, output="json"): + cmds = [{"command": command, "output": output}] + body = run_commands(module, cmds) + return body + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if new_key: + value = table.get(key) + if value: + new_dict[new_key] = str(value) + else: + new_dict[new_key] = value + return new_dict + + +def get_vtp_config(module): + command = "show vtp status" + + body = execute_show_command(command, module, "text")[0] + vtp_parsed = {} + + if body: + version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*" + domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*" + + try: + match_version = re.match(version_regex, body, re.DOTALL) + version = match_version.groupdict()["version"] + except AttributeError: + version = "" + + try: + match_domain = re.match(domain_regex, body, re.DOTALL) + domain = match_domain.groupdict()["domain"] + except AttributeError: + domain = "" + + if domain and version: + vtp_parsed["domain"] = domain + vtp_parsed["version"] = version + vtp_parsed["vtp_password"] = get_vtp_password(module) + + return vtp_parsed + + +def get_vtp_password(module): + command = "show vtp password" + output = "json" + cap = get_capabilities(module)["device_info"]["network_os_model"] + if re.search(r"Nexus 6", cap): + output = "text" + + body = execute_show_command(command, module, output)[0] + + if output == "json": + password = body.get("passwd", "") + else: + password = "" + rp = r"VTP Password: (\S+)" + mo = re.search(rp, body) + if mo: + password = mo.group(1) + + return str(password) + + +def main(): + argument_spec = dict( + vtp_password=dict(type="str", no_log=True), + state=dict(choices=["absent", "present"], default="present"), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + vtp_password = module.params["vtp_password"] or None + state = module.params["state"] + + existing = get_vtp_config(module) + end_state = existing + + args = dict(vtp_password=vtp_password) + + changed = False + proposed = dict((k, v) for k, v in args.items() if v is not None) + delta = dict(set(proposed.items()).difference(existing.items())) + + commands = [] + if state == "absent": + # if vtp_password is not set, some devices returns '\\' or the string 'None' + if ( + not existing["vtp_password"] + or existing["vtp_password"] == "\\" + or existing["vtp_password"] == "None" + ): + pass + elif vtp_password is not None: + if existing["vtp_password"] == proposed["vtp_password"]: + commands.append(["no vtp password"]) + else: + module.fail_json( + msg="Proposed vtp password doesn't match " + "current vtp password. It cannot be " + "removed when state=absent. If you are " + "trying to change the vtp password, use " + "state=present.", + ) + else: + if not existing.get("domain"): + module.fail_json(msg="Cannot remove a vtp password " "before vtp domain is set.") + + elif existing["vtp_password"] != ("\\"): + commands.append(["no vtp password"]) + + elif state == "present": + if delta: + if not existing.get("domain"): + module.fail_json(msg="Cannot set vtp password " "before vtp domain is set.") + + else: + commands.append(["vtp password {0}".format(vtp_password)]) + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + changed = True + load_config(module, cmds) + end_state = get_vtp_config(module) + if "configure" in cmds: + cmds.pop(0) + + results = {} + results["proposed"] = proposed + results["existing"] = existing + results["end_state"] = end_state + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py new file mode 100644 index 00000000..8c5305a0 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vtp_version +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages VTP version configuration. +description: +- Manages VTP version configuration. +version_added: 1.0.0 +author: +- Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- VTP feature must be active on the device to use this module. +- This module is used to manage only VTP version. +- Use this in combination with M(cisco.nxos.nxos_vtp_password) and M(cisco.nxos.nxos_vtp_version) + to fully manage VTP operations. +options: + version: + description: + - VTP version number. + required: true + choices: + - '1' + - '2' + type: str +""" +EXAMPLES = """ +# ENSURE VTP VERSION IS 2 +- cisco.nxos.nxos_vtp_version: + version: 2 + host: '{{ inventory_hostname }}' + username: '{{ un }}' + password: '{{ pwd }}' +""" + +RETURN = """ +proposed: + description: k/v pairs of parameters passed into module + returned: always + type: dict + sample: {"version": "2"} +existing: + description: + - k/v pairs of existing vtp + returned: always + type: dict + sample: {"domain": "testing", "version": "1", "vtp_password": "password"} +end_state: + description: k/v pairs of vtp after module execution + returned: always + type: dict + sample: {"domain": "testing", "version": "2", "vtp_password": "password"} +updates: + description: command sent to the device + returned: always + type: list + sample: ["vtp version 2"] +changed: + description: check to see if a change was made on the device + returned: always + type: bool + sample: true +""" +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_capabilities, + load_config, + run_commands, +) + + +def execute_show_command(command, module, output="json"): + cmds = [{"command": command, "output": output}] + body = run_commands(module, cmds) + return body + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def get_vtp_config(module): + command = "show vtp status" + body = execute_show_command(command, module, "text")[0] + vtp_parsed = {} + + if body: + version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*" + domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*" + + try: + match_version = re.match(version_regex, body, re.DOTALL) + version = match_version.groupdict()["version"] + except AttributeError: + version = "" + + try: + match_domain = re.match(domain_regex, body, re.DOTALL) + domain = match_domain.groupdict()["domain"] + except AttributeError: + domain = "" + + if domain and version: + vtp_parsed["domain"] = domain + vtp_parsed["version"] = version + vtp_parsed["vtp_password"] = get_vtp_password(module) + + return vtp_parsed + + +def get_vtp_password(module): + command = "show vtp password" + output = "json" + cap = get_capabilities(module)["device_info"]["network_os_model"] + if re.search(r"Nexus 6", cap): + output = "text" + + body = execute_show_command(command, module, output)[0] + + if output == "json": + password = body.get("passwd", "") + else: + password = "" + rp = r"VTP Password: (\S+)" + mo = re.search(rp, body) + if mo: + password = mo.group(1) + + return str(password) + + +def main(): + argument_spec = dict(version=dict(type="str", choices=["1", "2"], required=True)) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + + version = module.params["version"] + + existing = get_vtp_config(module) + end_state = existing + + args = dict(version=version) + + changed = False + proposed = dict((k, v) for k, v in args.items() if v is not None) + delta = dict(set(proposed.items()).difference(existing.items())) + + commands = [] + if delta: + commands.append(["vtp version {0}".format(version)]) + + cmds = flatten_list(commands) + if cmds: + if module.check_mode: + module.exit_json(changed=True, commands=cmds) + else: + changed = True + load_config(module, cmds) + end_state = get_vtp_config(module) + if "configure" in cmds: + cmds.pop(0) + + results = {} + results["proposed"] = proposed + results["existing"] = existing + results["end_state"] = end_state + results["updates"] = cmds + results["changed"] = changed + results["warnings"] = warnings + + module.exit_json(**results) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py new file mode 100644 index 00000000..ce311388 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py @@ -0,0 +1,458 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vxlan_vtep +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Manages VXLAN Network Virtualization Endpoint (NVE). +description: +- Manages VXLAN Network Virtualization Endpoint (NVE) overlay interface that terminates + VXLAN tunnels. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- The module is used to manage NVE properties, not to create NVE interfaces. Use M(cisco.nxos.nxos_interfaces) + if you wish to do so. +- C(state=absent) removes the interface. +- Default, where supported, restores params default value. +options: + interface: + description: + - Interface name for the VXLAN Network Virtualization Endpoint. + required: true + type: str + description: + description: + - Description of the NVE interface. + type: str + host_reachability: + description: + - Specify mechanism for host reachability advertisement. A Boolean value of 'true' + indicates that BGP will be used for host reachability advertisement. A Boolean + value of 'false' indicates that no protocol is used for host reachability advertisement. + Other host reachability advertisement protocols (e.g. OpenFlow, controller, etc.) are not + supported. + type: bool + shutdown: + description: + - Administratively shutdown the NVE interface. + type: bool + source_interface: + description: + - Specify the loopback interface whose IP address should be used for the NVE interface. + type: str + source_interface_hold_down_time: + description: + - Suppresses advertisement of the NVE loopback address until the overlay has converged. + type: str + global_mcast_group_L3: + description: + - Global multicast IP prefix for L3 VNIs or the keyword 'default'. This is available on + Nexus 9000 series switches running NX-OS software release 9.2(x) or higher. + type: str + global_mcast_group_L2: + description: + - Global multicast IP prefix for L2 VNIs or the keyword 'default'. This is available on + Nexus 9000 series switches running NX-OS software release 9.2(x) or higher. + type: str + global_suppress_arp: + description: + - Enables ARP suppression for all VNIs. This is available on NX-OS 9K series running + 9.2.x or higher. + type: bool + global_ingress_replication_bgp: + description: + - Configures ingress replication protocol as bgp for all VNIs. This is available on Nexus + 9000 series switches running NX-OS software release 9.2(x) or higher. + type: bool + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: + - present + - absent + type: str + multisite_border_gateway_interface: + description: + - Specify the loopback interface whose IP address should be used for the NVE + Multisite Border-gateway Interface. This is available on specific Nexus 9000 + series switches running NX-OS 7.0(3)I7(x) or higher. Specify "default" to remove + an existing gateway config. + type: str + version_added: 1.1.0 +""" +EXAMPLES = """ +- cisco.nxos.nxos_vxlan_vtep: + interface: nve1 + description: default + host_reachability: true + source_interface: Loopback0 + source_interface_hold_down_time: 30 + shutdown: default + multisite_border_gateway_interface: Loopback0 +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["interface nve1", "source-interface loopback0", + "source-interface hold-down-time 30", "description simple description", + "shutdown", "host-reachability protocol bgp", + "multisite border-gateway interface loopback0"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, + run_commands, +) + + +BOOL_PARAMS = [ + "shutdown", + "host_reachability", + "global_ingress_replication_bgp", + "global_suppress_arp", +] +PARAM_TO_COMMAND_KEYMAP = { + "description": "description", + "global_suppress_arp": "global suppress-arp", + "global_ingress_replication_bgp": "global ingress-replication protocol bgp", + "global_mcast_group_L3": "global mcast-group L3", + "global_mcast_group_L2": "global mcast-group L2", + "host_reachability": "host-reachability protocol bgp", + "interface": "interface", + "shutdown": "shutdown", + "source_interface": "source-interface", + "source_interface_hold_down_time": "source-interface hold-down-time", + "multisite_border_gateway_interface": "multisite border-gateway interface", +} +PARAM_TO_DEFAULT_KEYMAP = { + "description": False, + "shutdown": True, + "source_interface_hold_down_time": "180", +} + + +def get_value(arg, config, module): + if arg in BOOL_PARAMS: + REGEX = re.compile(r"\s+{0}\s*$".format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M) + NO_SHUT_REGEX = re.compile(r"\s+no shutdown\s*$", re.M) + value = False + if arg == "shutdown": + try: + if NO_SHUT_REGEX.search(config): + value = False + elif REGEX.search(config): + value = True + except TypeError: + value = False + else: + try: + if REGEX.search(config): + value = True + except TypeError: + value = False + else: + REGEX = re.compile( + r"(?:{0}\s)(?P<value>.*)$".format(PARAM_TO_COMMAND_KEYMAP[arg]), + re.M, + ) + NO_DESC_REGEX = re.compile(r"\s+{0}\s*$".format("no description"), re.M) + SOURCE_INTF_REGEX = re.compile( + r"(?:{0}\s)(?P<value>\S+)$".format(PARAM_TO_COMMAND_KEYMAP[arg]), + re.M, + ) + value = "" + if arg == "description": + if NO_DESC_REGEX.search(config): + value = False + elif PARAM_TO_COMMAND_KEYMAP[arg] in config: + value = REGEX.search(config).group("value").strip() + elif arg == "source_interface": + for line in config.splitlines(): + try: + if PARAM_TO_COMMAND_KEYMAP[arg] in config: + value = SOURCE_INTF_REGEX.search(config).group("value").strip() + break + except AttributeError: + value = "" + elif arg == "global_mcast_group_L2": + for line in config.splitlines(): + try: + if "global mcast-group" in line and "L2" in line: + value = line.split()[2].strip() + break + except AttributeError: + value = "" + elif arg == "global_mcast_group_L3": + for line in config.splitlines(): + try: + if "global mcast-group" in line and "L3" in line: + value = line.split()[2].strip() + break + except AttributeError: + value = "" + elif arg == "multisite_border_gateway_interface": + for line in config.splitlines(): + try: + if PARAM_TO_COMMAND_KEYMAP[arg] in config: + value = SOURCE_INTF_REGEX.search(config).group("value").strip() + break + except AttributeError: + value = "" + else: + if PARAM_TO_COMMAND_KEYMAP[arg] in config: + value = REGEX.search(config).group("value").strip() + return value + + +def get_existing(module, args): + existing = {} + netcfg = CustomNetworkConfig(indent=2, contents=get_config(module, flags=["all"])) + + interface_string = "interface {0}".format(module.params["interface"].lower()) + parents = [interface_string] + config = netcfg.get_section(parents) + + if config: + for arg in args: + existing[arg] = get_value(arg, config, module) + + existing["interface"] = module.params["interface"].lower() + else: + if interface_string in str(netcfg): + existing["interface"] = module.params["interface"].lower() + for arg in args: + existing[arg] = "" + return existing + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if new_key: + value = table.get(key) + if value: + new_dict[new_key] = value + else: + new_dict[new_key] = value + return new_dict + + +def fix_commands(commands, module): + source_interface_command = "" + no_source_interface_command = "" + no_host_reachability_command = "" + host_reachability_command = "" + + for command in commands: + if "no source-interface hold-down-time" in command: + pass + elif "source-interface hold-down-time" in command: + pass + elif "no source-interface" in command: + no_source_interface_command = command + elif "source-interface" in command: + source_interface_command = command + elif "no host-reachability" in command: + no_host_reachability_command = command + elif "host-reachability" in command: + host_reachability_command = command + + if host_reachability_command: + commands.pop(commands.index(host_reachability_command)) + commands.insert(0, host_reachability_command) + + if source_interface_command: + commands.pop(commands.index(source_interface_command)) + commands.insert(0, source_interface_command) + + if no_host_reachability_command: + commands.pop(commands.index(no_host_reachability_command)) + commands.append(no_host_reachability_command) + + if no_source_interface_command: + commands.pop(commands.index(no_source_interface_command)) + commands.append(no_source_interface_command) + + commands.insert(0, "terminal dont-ask") + return commands + + +def gsa_tcam_check(module): + """ + global_suppress_arp is an N9k-only command that requires TCAM resources. + This method checks the current TCAM allocation. + Note that changing tcam_size requires a switch reboot to take effect. + """ + cmds = [{"command": "show hardware access-list tcam region", "output": "json"}] + body = run_commands(module, cmds) + if body: + tcam_region = body[0]["TCAM_Region"]["TABLE_Sizes"]["ROW_Sizes"] + if bool( + [ + i + for i in tcam_region + if i["type"].startswith("Ingress ARP-Ether ACL") and i["tcam_size"] == "0" + ], + ): + msg = ( + "'show hardware access-list tcam region' indicates 'ARP-Ether' tcam size is 0 (no allocated resources). " + + "'global_suppress_arp' will be rejected by device." + ) + module.fail_json(msg=msg) + + +def state_present(module, existing, proposed, candidate): + commands = list() + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) + existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) + for key, value in proposed_commands.items(): + if value is True: + commands.append(key) + + elif value is False: + commands.append("no {0}".format(key)) + + elif value == "default": + if existing_commands.get(key): + existing_value = existing_commands.get(key) + if "global mcast-group" in key: + commands.append("no {0}".format(key)) + else: + commands.append("no {0} {1}".format(key, existing_value)) + else: + if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS: + commands.append("no {0}".format(key.lower())) + module.exit_json(commands=commands) + else: + if "L2" in key: + commands.append("global mcast-group " + value + " L2") + elif "L3" in key: + commands.append("global mcast-group " + value + " L3") + else: + command = "{0} {1}".format(key, value.lower()) + commands.append(command) + + if commands: + commands = fix_commands(commands, module) + parents = ["interface {0}".format(module.params["interface"].lower())] + candidate.add(commands, parents=parents) + else: + if not existing and module.params["interface"]: + commands = ["interface {0}".format(module.params["interface"].lower())] + candidate.add(commands, parents=[]) + + +def state_absent(module, existing, proposed, candidate): + commands = ["no interface {0}".format(module.params["interface"].lower())] + candidate.add(commands, parents=[]) + + +def main(): + argument_spec = dict( + interface=dict(required=True, type="str"), + description=dict(required=False, type="str"), + host_reachability=dict(required=False, type="bool"), + global_ingress_replication_bgp=dict(required=False, type="bool"), + global_suppress_arp=dict(required=False, type="bool"), + global_mcast_group_L2=dict(required=False, type="str"), + global_mcast_group_L3=dict(required=False, type="str"), + shutdown=dict(required=False, type="bool"), + source_interface=dict(required=False, type="str"), + source_interface_hold_down_time=dict(required=False, type="str"), + state=dict(choices=["present", "absent"], default="present", required=False), + multisite_border_gateway_interface=dict(required=False, type="str"), + ) + + mutually_exclusive = [("global_ingress_replication_bgp", "global_mcast_group_L2")] + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + warnings = list() + result = {"changed": False, "commands": [], "warnings": warnings} + + state = module.params["state"] + + args = PARAM_TO_COMMAND_KEYMAP.keys() + + existing = get_existing(module, args) + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + proposed = {} + for key, value in proposed_args.items(): + if key != "interface": + if str(value).lower() == "default": + value = PARAM_TO_DEFAULT_KEYMAP.get(key) + if value is None: + if key in BOOL_PARAMS: + value = False + else: + value = "default" + if str(existing.get(key)).lower() != str(value).lower(): + proposed[key] = value + + candidate = CustomNetworkConfig(indent=3) + + if proposed.get("global_suppress_arp"): + gsa_tcam_check(module) + if state == "present": + if not existing: + warnings.append( + "The proposed NVE interface did not exist. " + "It's recommended to use nxos_interfaces to create " + "all logical interfaces.", + ) + state_present(module, existing, proposed, candidate) + elif state == "absent" and existing: + state_absent(module, existing, proposed, candidate) + + if candidate: + candidate = candidate.items_text() + result["commands"] = candidate + result["changed"] = True + load_config(module, candidate) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py new file mode 100644 index 00000000..d58bd6c9 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py @@ -0,0 +1,452 @@ +#!/usr/bin/python +# +# 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 = """ +module: nxos_vxlan_vtep_vni +extends_documentation_fragment: +- cisco.nxos.nxos +short_description: Creates a Virtual Network Identifier member (VNI) +description: +- Creates a Virtual Network Identifier member (VNI) for an NVE overlay interface. +version_added: 1.0.0 +author: Gabriele Gerbino (@GGabriele) +notes: +- Tested against NXOSv 7.3.(0)D1(1) on VIRL +- Unsupported for Cisco MDS +- default, where supported, restores params default value. +options: + interface: + description: + - Interface name for the VXLAN Network Virtualization Endpoint. + required: true + type: str + vni: + description: + - ID of the Virtual Network Identifier. + required: true + type: str + assoc_vrf: + description: + - This attribute is used to identify and separate processing VNIs that are associated + with a VRF and used for routing. The VRF and VNI specified with this command + must match the configuration of the VNI under the VRF. + type: bool + ingress_replication: + description: + - Specifies mechanism for host reachability advertisement. + choices: + - bgp + - static + - default + type: str + multicast_group: + description: + - The multicast group (range) of the VNI. Valid values are string and keyword + 'default'. + type: str + peer_list: + description: + - Set the ingress-replication static peer list. Valid values are an array, a space-separated + string of ip addresses, or the keyword 'default'. + type: list + elements: str + suppress_arp: + description: + - Suppress arp under layer 2 VNI. + type: bool + suppress_arp_disable: + description: + - Overrides the global ARP suppression config. This is available on NX-OS 9K series + running 9.2.x or higher. + type: bool + state: + description: + - Determines whether the config should be present or not on the device. + default: present + choices: + - present + - absent + type: str + multisite_ingress_replication: + description: + - Enables multisite ingress replication. + choices: + - disable + - enable + - optimized + type: str + version_added: 1.1.0 +""" +EXAMPLES = """ +- cisco.nxos.nxos_vxlan_vtep_vni: + interface: nve1 + vni: 6000 + ingress_replication: default + multisite_ingress_replication: enable +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: ["interface nve1", "member vni 6000", "multisite ingress-replication"] +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + CustomNetworkConfig, +) + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + get_config, + load_config, +) + + +BOOL_PARAMS = ["assoc_vrf", "suppress_arp", "suppress_arp_disable"] +PARAM_TO_DEFAULT_KEYMAP = { + "multicast_group": "", + "peer_list": [], + "ingress_replication": "", +} +PARAM_TO_COMMAND_KEYMAP = { + "assoc_vrf": "associate-vrf", + "interface": "interface", + "vni": "member vni", + "ingress_replication": "ingress-replication protocol", + "multicast_group": "mcast-group", + "peer_list": "peer-ip", + "suppress_arp": "suppress-arp", + "suppress_arp_disable": "suppress-arp disable", + "multisite_ingress_replication": "multisite ingress-replication", +} + + +def get_value(arg, config, module): + command = PARAM_TO_COMMAND_KEYMAP[arg] + command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M) + + if arg in BOOL_PARAMS: + command_re = re.compile(r"\s+{0}\s*$".format(command), re.M) + value = False + if command_re.search(config): + value = True + elif arg == "peer_list": + has_command_val = command_val_re.findall(config, re.M) + value = [] + if has_command_val: + value = has_command_val + elif arg == "multisite_ingress_replication": + has_command = re.search(r"^\s+{0}$".format(command), config, re.M) + has_command_val = command_val_re.search(config, re.M) + value = "disable" + if has_command: + value = "enable" + elif has_command_val: + value = "optimized" + else: + value = "" + has_command_val = command_val_re.search(config, re.M) + if has_command_val: + value = has_command_val.group("value") + return value + + +def check_interface(module, netcfg): + config = str(netcfg) + + has_interface = re.search(r"(?:interface nve)(?P<value>.*)$", config, re.M) + value = "" + if has_interface: + value = "nve{0}".format(has_interface.group("value")) + + return value + + +def get_existing(module, args): + existing = {} + netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) + + interface_exist = check_interface(module, netcfg) + if interface_exist: + parents = ["interface {0}".format(interface_exist)] + temp_config = netcfg.get_section(parents) + + if "member vni {0} associate-vrf".format(module.params["vni"]) in temp_config: + parents.append("member vni {0} associate-vrf".format(module.params["vni"])) + config = netcfg.get_section(parents) + elif "member vni {0}".format(module.params["vni"]) in temp_config: + parents.append("member vni {0}".format(module.params["vni"])) + config = netcfg.get_section(parents) + else: + config = {} + + if config: + for arg in args: + if arg not in ["interface", "vni"]: + existing[arg] = get_value(arg, config, module) + existing["interface"] = interface_exist + existing["vni"] = module.params["vni"] + + return existing, interface_exist + + +def apply_key_map(key_map, table): + new_dict = {} + for key, value in table.items(): + new_key = key_map.get(key) + if new_key: + new_dict[new_key] = value + return new_dict + + +def state_present(module, existing, proposed, candidate): + commands = list() + proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) + existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) + + for key, value in proposed_commands.items(): + if key == "associate-vrf": + command = "member vni {0} {1}".format(module.params["vni"], key) + if not value: + command = "no {0}".format(command) + commands.append(command) + + elif key == "peer-ip" and value != []: + for peer in value: + commands.append("{0} {1}".format(key, peer)) + + elif key == "mcast-group" and value != existing_commands.get(key): + commands.append("no {0}".format(key)) + vni_command = "member vni {0}".format(module.params["vni"]) + if vni_command not in commands: + commands.append("member vni {0}".format(module.params["vni"])) + if value != PARAM_TO_DEFAULT_KEYMAP.get("multicast_group", "default"): + commands.append("{0} {1}".format(key, value)) + + elif key == "ingress-replication protocol" and value != existing_commands.get(key): + evalue = existing_commands.get(key) + dvalue = PARAM_TO_DEFAULT_KEYMAP.get("ingress_replication", "default") + if value != dvalue: + if evalue and evalue != dvalue: + commands.append("no {0} {1}".format(key, evalue)) + commands.append("{0} {1}".format(key, value)) + else: + if evalue: + commands.append("no {0} {1}".format(key, evalue)) + + elif value is True: + commands.append(key) + elif value is False: + commands.append("no {0}".format(key)) + elif value == "default" or value == []: + if existing_commands.get(key): + existing_value = existing_commands.get(key) + if key == "peer-ip": + for peer in existing_value: + commands.append("no {0} {1}".format(key, peer)) + else: + commands.append("no {0} {1}".format(key, existing_value)) + else: + if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS: + commands.append("no {0}".format(key.lower())) + elif key == "multisite ingress-replication" and value != existing_commands.get(key): + vni_command = "member vni {0}".format(module.params["vni"]) + if vni_command not in commands: + commands.append("member vni {0}".format(module.params["vni"])) + if value == "disable": + command = "no {0}".format(key) + commands.append(command) + elif value == "enable": + command = "{0}".format(key) + commands.append(command) + elif value == "optimized": + command = "{0} {1}".format(key, value) + commands.append(command) + else: + command = "{0} {1}".format(key, value.lower()) + commands.append(command) + + if commands: + vni_command = "member vni {0}".format(module.params["vni"]) + ingress_replications_command = "ingress-replication protocol static" + ingress_replicationb_command = "ingress-replication protocol bgp" + ingress_replicationns_command = "no ingress-replication protocol static" + ingress_replicationnb_command = "no ingress-replication protocol bgp" + interface_command = "interface {0}".format(module.params["interface"]) + + if any( + c in commands + for c in ( + ingress_replications_command, + ingress_replicationb_command, + ingress_replicationnb_command, + ingress_replicationns_command, + ) + ): + static_level_cmds = [cmd for cmd in commands if "peer" in cmd] + parents = [interface_command, vni_command] + commands = [cmd for cmd in commands if "peer" not in cmd] + for cmd in commands: + parents.append(cmd) + candidate.add(static_level_cmds, parents=parents) + + elif "peer-ip" in commands[0]: + static_level_cmds = list(commands) + parents = [ + interface_command, + vni_command, + ingress_replications_command, + ] + candidate.add(static_level_cmds, parents=parents) + + if vni_command in commands: + parents = [interface_command] + commands.remove(vni_command) + if module.params["assoc_vrf"] is None: + parents.append(vni_command) + candidate.add(commands, parents=parents) + + +def state_absent(module, existing, proposed, candidate): + if existing["assoc_vrf"]: + commands = ["no member vni {0} associate-vrf".format(module.params["vni"])] + else: + commands = ["no member vni {0}".format(module.params["vni"])] + parents = ["interface {0}".format(module.params["interface"])] + candidate.add(commands, parents=parents) + + +def main(): + argument_spec = dict( + interface=dict(required=True, type="str"), + vni=dict(required=True, type="str"), + assoc_vrf=dict(required=False, type="bool"), + multicast_group=dict(required=False, type="str"), + peer_list=dict(required=False, type="list", elements="str"), + suppress_arp=dict(required=False, type="bool"), + suppress_arp_disable=dict(required=False, type="bool"), + ingress_replication=dict(required=False, type="str", choices=["bgp", "static", "default"]), + state=dict(choices=["present", "absent"], default="present", required=False), + multisite_ingress_replication=dict( + required=False, + type="str", + choices=["enable", "optimized", "disable"], + ), + ) + + mutually_exclusive = [ + ("suppress_arp", "suppress_arp_disable"), + ("assoc_vrf", "multicast_group"), + ("assoc_vrf", "suppress_arp"), + ("assoc_vrf", "suppress_arp_disable"), + ("assoc_vrf", "ingress_replication"), + ] + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + warnings = list() + result = {"changed": False, "commands": [], "warnings": warnings} + + if module.params["peer_list"]: + if ( + module.params["peer_list"][0] != "default" + and module.params["ingress_replication"] != "static" + ): + module.fail_json( + msg="ingress_replication=static is required " "when using peer_list param", + ) + else: + peer_list = module.params["peer_list"] + if peer_list[0] == "default": + module.params["peer_list"] = "default" + else: + stripped_peer_list = list(map(str.strip, peer_list)) + module.params["peer_list"] = stripped_peer_list + + if ( + module.params["multisite_ingress_replication"] == "enable" + or module.params["multisite_ingress_replication"] == "optimized" + ): + if module.params["ingress_replication"] == "static": + module.fail_json( + msg="ingress_replication=static is not allowed " + "when using multisite_ingress_replication", + ) + + state = module.params["state"] + args = PARAM_TO_COMMAND_KEYMAP.keys() + existing, interface_exist = get_existing(module, args) + + if state == "present": + if not interface_exist: + module.fail_json( + msg="The proposed NVE interface does not exist. Use nxos_interface to create it first.", + ) + elif interface_exist != module.params["interface"]: + module.fail_json(msg="Only 1 NVE interface is allowed on the switch.") + elif state == "absent": + if interface_exist != module.params["interface"]: + module.exit_json(**result) + elif existing and existing["vni"] != module.params["vni"]: + module.fail_json( + msg="ERROR: VNI delete failed: Could not find vni node for {0}".format( + module.params["vni"], + ), + existing_vni=existing["vni"], + ) + + proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args) + + proposed = {} + for key, value in proposed_args.items(): + if key in ["multicast_group", "peer_list", "ingress_replication"]: + if str(value).lower() == "default": + value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default") + if key != "interface" and existing.get(key) != value: + proposed[key] = value + + candidate = CustomNetworkConfig(indent=3) + if state == "present": + state_present(module, existing, proposed, candidate) + elif existing and state == "absent": + state_absent(module, existing, proposed, candidate) + + if candidate: + candidate = candidate.items_text() + result["changed"] = True + result["commands"] = candidate + if not module.check_mode: + load_config(module, candidate) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py new file mode 100644 index 00000000..7c9fba30 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py @@ -0,0 +1,888 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or +# https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +DOCUMENTATION = """ +module: nxos_zone_zoneset +short_description: Configuration of zone/zoneset for Cisco NXOS MDS Switches. +description: +- Configuration of zone/zoneset for Cisco MDS NXOS. +version_added: 1.0.0 +author: +- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com) +notes: +- Tested against Cisco MDS NX-OS 8.4(1) +options: + zone_zoneset_details: + description: + - List of zone/zoneset details to be added or removed + type: list + elements: dict + suboptions: + vsan: + description: + - vsan id + required: true + type: int + mode: + description: + - mode of the zone for the vsan + choices: + - enhanced + - basic + type: str + default_zone: + description: + - default zone behaviour for the vsan + choices: + - permit + - deny + type: str + smart_zoning: + description: + - Removes the vsan if True + type: bool + zone: + description: + - List of zone options for that vsan + type: list + elements: dict + suboptions: + name: + description: + - name of the zone + required: true + type: str + remove: + description: + - Deletes the zone if True + type: bool + default: false + members: + description: + - Members of the zone that needs to be removed or added + type: list + elements: dict + suboptions: + pwwn: + description: + - pwwn member of the zone, use alias 'device_alias' as option for + device_alias member + aliases: + - device_alias + required: true + type: str + remove: + description: + - Removes member from the zone if True + type: bool + default: false + devtype: + description: + - devtype of the zone member used along with Smart zoning config + choices: + - initiator + - target + - both + type: str + zoneset: + description: + - List of zoneset options for the vsan + type: list + elements: dict + suboptions: + name: + description: + - name of the zoneset + required: true + type: str + remove: + description: + - Removes zoneset if True + type: bool + default: false + action: + description: + - activates/de-activates the zoneset + choices: + - activate + - deactivate + type: str + members: + description: + - Members of the zoneset that needs to be removed or added + type: list + elements: dict + suboptions: + name: + description: + - name of the zone that needs to be added to the zoneset or removed + from the zoneset + required: true + type: str + remove: + description: + - Removes zone member from the zoneset + type: bool + default: false +""" + +EXAMPLES = """ +- name: Test that zone/zoneset module works + cisco.nxos.nxos_zone_zoneset: + zone_zoneset_details: + - mode: enhanced + vsan: 22 + zone: + - members: + - pwwn: 11:11:11:11:11:11:11:11 + - device_alias: test123 + - pwwn: 61:61:62:62:12:12:12:12 + remove: true + name: zoneA + - members: + - pwwn: 10:11:11:11:11:11:11:11 + - pwwn: 62:62:62:62:21:21:21:21 + name: zoneB + - name: zoneC + remove: true + zoneset: + - action: activate + members: + - name: zoneA + - name: zoneB + - name: zoneC + remove: true + name: zsetname1 + - action: deactivate + name: zsetTestExtra + remove: true + - mode: basic + smart_zoning: true + vsan: 21 + zone: + - members: + - devtype: both + pwwn: 11:11:11:11:11:11:11:11 + - pwwn: 62:62:62:62:12:12:12:12 + - devtype: both + pwwn: 92:62:62:62:12:12:1a:1a + remove: true + name: zone21A + - members: + - pwwn: 10:11:11:11:11:11:11:11 + - pwwn: 62:62:62:62:21:21:21:21 + name: zone21B + zoneset: + - action: activate + members: + - name: zone21A + - name: zone21B + name: zsetname212 + +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: + - terminal dont-ask + - zone name zoneA vsan 923 + - member pwwn 11:11:11:11:11:11:11:11 + - no member device-alias test123 + - zone commit vsan 923 + - no terminal dont-ask +messages: + description: debug messages + returned: always + type: list + sample: + - "zone mode is already enhanced ,no change in zone mode configuration for vsan 922" + - "zone member '11:11:11:11:11:11:11:11' is already present in zone 'zoneA' in vsan 922 hence nothing to add" + - "zone member 'test123' is already present in zone 'zoneA' in vsan 922 hence nothing to add" + - "zone member '61:61:62:62:12:12:12:12' is not present in zone 'zoneA' in vsan 922 hence nothing to remove" + - "zone member '10:11:11:11:11:11:11:11' is already present in zone 'zoneB' in vsan 922 hence nothing to add" + - "zone member '62:62:62:62:21:21:21:21' is already present in zone 'zoneB' in vsan 922 hence nothing to add" + - "zone 'zoneC' is not present in vsan 922 , so nothing to remove" +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +__metaclass__ = type + + +class ShowZonesetActive(object): + """docstring for ShowZonesetActive""" + + def __init__(self, module, vsan): + self.vsan = vsan + self.module = module + self.activeZSName = None + self.parseCmdOutput() + + def execute_show_zoneset_active_cmd(self): + command = "show zoneset active vsan " + str(self.vsan) + " | grep zoneset" + output = execute_show_command(command, self.module)[0] + return output + + def parseCmdOutput(self): + patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan) + output = self.execute_show_zoneset_active_cmd().split("\n") + if len(output) == 0: + return + else: + for line in output: + line = line.strip() + mzs = re.match(patZoneset, line.strip()) + if mzs: + self.activeZSName = mzs.group(1).strip() + return + + def isZonesetActive(self, zsname): + if zsname == self.activeZSName: + return True + return False + + +class ShowZoneset(object): + """docstring for ShowZoneset""" + + def __init__(self, module, vsan): + self.vsan = vsan + self.module = module + self.zsDetails = {} + self.parseCmdOutput() + + def execute_show_zoneset_cmd(self): + command = "show zoneset vsan " + str(self.vsan) + output = execute_show_command(command, self.module)[0] + return output + + def parseCmdOutput(self): + patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan) + patZone = r"zone name (\S+) vsan " + str(self.vsan) + output = self.execute_show_zoneset_cmd().split("\n") + for line in output: + line = line.strip() + mzs = re.match(patZoneset, line.strip()) + mz = re.match(patZone, line.strip()) + if mzs: + zonesetname = mzs.group(1).strip() + self.zsDetails[zonesetname] = [] + continue + elif mz: + zonename = mz.group(1).strip() + v = self.zsDetails[zonesetname] + v.append(zonename) + self.zsDetails[zonesetname] = v + + def isZonesetPresent(self, zsname): + return zsname in self.zsDetails.keys() + + def isZonePresentInZoneset(self, zsname, zname): + if zsname in self.zsDetails.keys(): + return zname in self.zsDetails[zsname] + return False + + +class ShowZone(object): + """docstring for ShowZone""" + + def __init__(self, module, vsan): + self.vsan = vsan + self.module = module + self.zDetails = {} + self.parseCmdOutput() + + def execute_show_zone_vsan_cmd(self): + command = "show zone vsan " + str(self.vsan) + output = execute_show_command(command, self.module)[0] + return output + + def parseCmdOutput(self): + patZone = r"zone name (\S+) vsan " + str(self.vsan) + output = self.execute_show_zone_vsan_cmd().split("\n") + for line in output: + line = re.sub(r"[\[].*?[\]]", "", line) + line = " ".join(line.strip().split()) + if "init" in line: + line = line.replace("init", "initiator") + m = re.match(patZone, line) + if m: + zonename = m.group(1).strip() + self.zDetails[zonename] = [] + continue + else: + # For now we support only pwwn and device-alias under zone + # Ideally should use 'supported_choices'....but maybe next + # time. + if "pwwn" in line or "device-alias" in line: + v = self.zDetails[zonename] + v.append(line) + self.zDetails[zonename] = v + + def isZonePresent(self, zname): + return zname in self.zDetails.keys() + + def isZoneMemberPresent(self, zname, cmd): + if zname in self.zDetails.keys(): + zonememlist = self.zDetails[zname] + for eachline in zonememlist: + if cmd in eachline: + return True + return False + + def get_zDetails(self): + return self.zDetails + + +class ShowZoneStatus(object): + """docstring for ShowZoneStatus""" + + def __init__(self, module, vsan): + self.vsan = vsan + self.vsanAbsent = False + self.module = module + self.default_zone = "" + self.mode = "" + self.session = "" + self.sz = "" + self.locked = False + self.update() + + def execute_show_zone_status_cmd(self): + command = "show zone status vsan " + str(self.vsan) + output = execute_show_command(command, self.module)[0] + return output + + def update(self): + output = self.execute_show_zone_status_cmd().split("\n") + + patfordefzone = "VSAN: " + str(self.vsan) + r" default-zone:\s+(\S+).*" + patformode = r".*mode:\s+(\S+).*" + patforsession = r"^session:\s+(\S+).*" + patforsz = r".*smart-zoning:\s+(\S+).*" + for line in output: + if "is not configured" in line: + self.vsanAbsent = True + break + mdefz = re.match(patfordefzone, line.strip()) + mmode = re.match(patformode, line.strip()) + msession = re.match(patforsession, line.strip()) + msz = re.match(patforsz, line.strip()) + + if mdefz: + self.default_zone = mdefz.group(1) + if mmode: + self.mode = mmode.group(1) + if msession: + self.session = msession.group(1) + if self.session != "none": + self.locked = True + if msz: + self.sz = msz.group(1) + + def isLocked(self): + return self.locked + + def getDefaultZone(self): + return self.default_zone + + def getMode(self): + return self.mode + + def getSmartZoningStatus(self): + return self.sz + + def isVsanAbsent(self): + return self.vsanAbsent + + +def execute_show_command(command, module, command_type="cli_show"): + output = "text" + commands = [{"command": command, "output": output}] + return run_commands(module, commands) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def getMemType(supported_choices, allmemkeys, default="pwwn"): + for eachchoice in supported_choices: + if eachchoice in allmemkeys: + return eachchoice + return default + + +def main(): + supported_choices = ["device_alias"] + zone_member_spec = dict( + pwwn=dict(required=True, type="str", aliases=["device_alias"]), + devtype=dict(type="str", choices=["initiator", "target", "both"]), + remove=dict(type="bool", default=False), + ) + + zone_spec = dict( + name=dict(required=True, type="str"), + members=dict(type="list", elements="dict", options=zone_member_spec), + remove=dict(type="bool", default=False), + ) + + zoneset_member_spec = dict( + name=dict(required=True, type="str"), + remove=dict(type="bool", default=False), + ) + + zoneset_spec = dict( + name=dict(type="str", required=True), + members=dict(type="list", elements="dict", options=zoneset_member_spec), + remove=dict(type="bool", default=False), + action=dict(type="str", choices=["activate", "deactivate"]), + ) + + zonedetails_spec = dict( + vsan=dict(required=True, type="int"), + mode=dict(type="str", choices=["enhanced", "basic"]), + default_zone=dict(type="str", choices=["permit", "deny"]), + smart_zoning=dict(type="bool"), + zone=dict(type="list", elements="dict", options=zone_spec), + zoneset=dict(type="list", elements="dict", options=zoneset_spec), + ) + + argument_spec = dict( + zone_zoneset_details=dict(type="list", elements="dict", options=zonedetails_spec), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + messages = list() + commands = list() + result = {"changed": False} + + commands_executed = [] + listOfZoneDetails = module.params["zone_zoneset_details"] + for eachZoneZonesetDetail in listOfZoneDetails: + vsan = eachZoneZonesetDetail["vsan"] + op_mode = eachZoneZonesetDetail["mode"] + op_default_zone = eachZoneZonesetDetail["default_zone"] + op_smart_zoning = eachZoneZonesetDetail["smart_zoning"] + op_zone = eachZoneZonesetDetail["zone"] + op_zoneset = eachZoneZonesetDetail["zoneset"] + + # Step1: execute show zone status and get + shZoneStatusObj = ShowZoneStatus(module, vsan) + sw_default_zone = shZoneStatusObj.getDefaultZone() + sw_mode = shZoneStatusObj.getMode() + sw_smart_zoning = shZoneStatusObj.getSmartZoningStatus() + + if sw_smart_zoning.lower() == "Enabled".lower(): + sw_smart_zoning_bool = True + else: + sw_smart_zoning_bool = False + + if shZoneStatusObj.isVsanAbsent(): + module.fail_json( + msg="Vsan " + str(vsan) + " is not present in the switch. Hence cannot procced.", + ) + + if shZoneStatusObj.isLocked(): + module.fail_json( + msg="zone has acquired lock on the switch for vsan " + + str(vsan) + + ". Hence cannot procced.", + ) + + # Process zone default zone options + if op_default_zone is not None: + if op_default_zone != sw_default_zone: + if op_default_zone == "permit": + commands_executed.append("zone default-zone permit vsan " + str(vsan)) + messages.append( + "default zone configuration changed from deny to permit for vsan " + + str(vsan), + ) + else: + commands_executed.append("no zone default-zone permit vsan " + str(vsan)) + messages.append( + "default zone configuration changed from permit to deny for vsan " + + str(vsan), + ) + else: + messages.append( + "default zone is already " + + op_default_zone + + " ,no change in default zone configuration for vsan " + + str(vsan), + ) + + # Process zone mode options + if op_mode is not None: + if op_mode != sw_mode: + if op_mode == "enhanced": + commands_executed.append("zone mode enhanced vsan " + str(vsan)) + messages.append( + "zone mode configuration changed from basic to enhanced for vsan " + + str(vsan), + ) + else: + commands_executed.append("no zone mode enhanced vsan " + str(vsan)) + messages.append( + "zone mode configuration changed from enhanced to basic for vsan " + + str(vsan), + ) + else: + messages.append( + "zone mode is already " + + op_mode + + " ,no change in zone mode configuration for vsan " + + str(vsan), + ) + + # Process zone smart-zone options + if op_smart_zoning is not None: + if op_smart_zoning != sw_smart_zoning_bool: + if op_smart_zoning: + commands_executed.append("zone smart-zoning enable vsan " + str(vsan)) + messages.append("smart-zoning enabled for vsan " + str(vsan)) + else: + commands_executed.append("no zone smart-zoning enable vsan " + str(vsan)) + messages.append("smart-zoning disabled for vsan " + str(vsan)) + else: + messages.append( + "smart-zoning is already set to " + + sw_smart_zoning + + " , no change in smart-zoning configuration for vsan " + + str(vsan), + ) + + # Process zone member options + # TODO: Obviously this needs to be cleaned up properly, as there are a lot of ifelse statements which is bad + # Will take it up later becoz of time constraints + if op_zone is not None: + shZoneObj = ShowZone(module, vsan) + for eachzone in op_zone: + zname = eachzone["name"] + zmembers = eachzone["members"] + removeflag = eachzone["remove"] + if removeflag: + if shZoneObj.isZonePresent(zname): + messages.append("zone '" + zname + "' is removed from vsan " + str(vsan)) + commands_executed.append("no zone name " + zname + " vsan " + str(vsan)) + else: + messages.append( + "zone '" + + zname + + "' is not present in vsan " + + str(vsan) + + " , so nothing to remove", + ) + else: + if zmembers is None: + if shZoneObj.isZonePresent(zname): + messages.append( + "zone '" + zname + "' is already present in vsan " + str(vsan), + ) + else: + commands_executed.append("zone name " + zname + " vsan " + str(vsan)) + messages.append("zone '" + zname + "' is created in vsan " + str(vsan)) + else: + cmdmemlist = [] + for eachmem in zmembers: + memtype = getMemType(supported_choices, eachmem.keys()) + cmd = memtype.replace("_", "-") + " " + eachmem[memtype] + if op_smart_zoning or sw_smart_zoning_bool: + if eachmem["devtype"] is not None: + cmd = cmd + " " + eachmem["devtype"] + if eachmem["remove"]: + if shZoneObj.isZonePresent(zname): + if shZoneObj.isZoneMemberPresent(zname, cmd): + cmd = "no member " + cmd + cmdmemlist.append(cmd) + if op_smart_zoning and eachmem["devtype"] is not None: + messages.append( + "removing zone member '" + + eachmem[memtype] + + " of device type '" + + eachmem["devtype"] + + "' from zone '" + + zname + + "' in vsan " + + str(vsan), + ) + else: + messages.append( + "removing zone member '" + + eachmem[memtype] + + "' from zone '" + + zname + + "' in vsan " + + str(vsan), + ) + else: + if op_smart_zoning and eachmem["devtype"] is not None: + messages.append( + "zone member '" + + eachmem[memtype] + + "' of device type '" + + eachmem["devtype"] + + "' is not present in zone '" + + zname + + "' in vsan " + + str(vsan) + + " hence nothing to remove", + ) + else: + messages.append( + "zone member '" + + eachmem[memtype] + + "' is not present in zone '" + + zname + + "' in vsan " + + str(vsan) + + " hence nothing to remove", + ) + else: + messages.append( + "zone '" + + zname + + "' is not present in vsan " + + str(vsan) + + " , hence cannot remove the members", + ) + + else: + if shZoneObj.isZoneMemberPresent(zname, cmd): + if op_smart_zoning and eachmem["devtype"] is not None: + messages.append( + "zone member '" + + eachmem[memtype] + + "' of device type '" + + eachmem["devtype"] + + "' is already present in zone '" + + zname + + "' in vsan " + + str(vsan) + + " hence nothing to add", + ) + else: + messages.append( + "zone member '" + + eachmem[memtype] + + "' is already present in zone '" + + zname + + "' in vsan " + + str(vsan) + + " hence nothing to add", + ) + else: + cmd = "member " + cmd + cmdmemlist.append(cmd) + if op_smart_zoning and eachmem["devtype"] is not None: + messages.append( + "adding zone member '" + + eachmem[memtype] + + "' of device type '" + + eachmem["devtype"] + + "' to zone '" + + zname + + "' in vsan " + + str(vsan), + ) + else: + messages.append( + "adding zone member '" + + eachmem[memtype] + + "' to zone '" + + zname + + "' in vsan " + + str(vsan), + ) + if len(cmdmemlist) != 0: + commands_executed.append("zone name " + zname + " vsan " + str(vsan)) + commands_executed = commands_executed + cmdmemlist + + # Process zoneset member options + if op_zoneset is not None: + dactcmd = [] + actcmd = [] + shZonesetObj = ShowZoneset(module, vsan) + shZonesetActiveObj = ShowZonesetActive(module, vsan) + for eachzoneset in op_zoneset: + zsetname = eachzoneset["name"] + zsetmembers = eachzoneset["members"] + removeflag = eachzoneset["remove"] + actionflag = eachzoneset["action"] + if removeflag: + if shZonesetObj.isZonesetPresent(zsetname): + messages.append( + "zoneset '" + zsetname + "' is removed from vsan " + str(vsan), + ) + commands_executed.append( + "no zoneset name " + zsetname + " vsan " + str(vsan), + ) + else: + messages.append( + "zoneset '" + + zsetname + + "' is not present in vsan " + + str(vsan) + + " ,hence there is nothing to remove", + ) + else: + if zsetmembers is not None: + cmdmemlist = [] + for eachzsmem in zsetmembers: + zsetmem_name = eachzsmem["name"] + zsetmem_removeflag = eachzsmem["remove"] + if zsetmem_removeflag: + if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name): + cmd = "no member " + zsetmem_name + cmdmemlist.append(cmd) + messages.append( + "removing zoneset member '" + + zsetmem_name + + "' from zoneset '" + + zsetname + + "' in vsan " + + str(vsan), + ) + else: + messages.append( + "zoneset member '" + + zsetmem_name + + "' is not present in zoneset '" + + zsetname + + "' in vsan " + + str(vsan) + + " ,hence there is nothing to remove", + ) + else: + if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name): + messages.append( + "zoneset member '" + + zsetmem_name + + "' is already present in zoneset '" + + zsetname + + "' in vsan " + + str(vsan) + + " ,hence there is nothing to add", + ) + else: + cmd = "member " + zsetmem_name + cmdmemlist.append(cmd) + messages.append( + "adding zoneset member '" + + zsetmem_name + + "' to zoneset '" + + zsetname + + "' in vsan " + + str(vsan), + ) + if len(cmdmemlist) != 0: + commands_executed.append( + "zoneset name " + zsetname + " vsan " + str(vsan), + ) + commands_executed = commands_executed + cmdmemlist + else: + if shZonesetObj.isZonesetPresent(zsetname): + messages.append( + "zoneset '" + + zsetname + + "' is already present in vsan " + + str(vsan), + ) + else: + commands_executed.append( + "zoneset name " + zsetname + " vsan " + str(vsan), + ) + messages.append( + "zoneset '" + zsetname + "' is created in vsan " + str(vsan), + ) + + # Process zoneset activate options + if actionflag == "deactivate": + if shZonesetActiveObj.isZonesetActive(zsetname): + messages.append( + "deactivating zoneset '" + zsetname + "' in vsan " + str(vsan), + ) + dactcmd.append( + "no zoneset activate name " + zsetname + " vsan " + str(vsan), + ) + else: + messages.append( + "zoneset '" + + zsetname + + "' in vsan " + + str(vsan) + + " is not activated, hence cannot deactivate", + ) + elif actionflag == "activate": + if commands_executed: + messages.append( + "activating zoneset '" + zsetname + "' in vsan " + str(vsan), + ) + actcmd.append("zoneset activate name " + zsetname + " vsan " + str(vsan)) + else: + messages.append( + "no changes to existing zoneset '" + + zsetname + + "' in vsan " + + str(vsan) + + " hence activate action is ignored", + ) + commands_executed = commands_executed + dactcmd + actcmd + + if commands_executed: + if op_mode == "enhanced": + commands_executed.append("zone commit vsan " + str(vsan)) + elif op_mode is None: + if sw_mode == "enhanced": + commands_executed.append("zone commit vsan " + str(vsan)) + + if commands_executed: + commands_executed = ["terminal dont-ask"] + commands_executed + ["no terminal dont-ask"] + + cmds = flatten_list(commands_executed) + if cmds: + if module.check_mode: + module.exit_json( + changed=False, + commands=cmds, + msg="Check Mode: No cmds issued to the hosts", + ) + else: + result["changed"] = True + commands = commands + cmds + load_config(module, cmds) + + result["messages"] = messages + result["commands"] = commands_executed + result["warnings"] = warnings + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py b/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py new file mode 100644 index 00000000..71d4ebb6 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py @@ -0,0 +1,550 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or +# https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +DOCUMENTATION = """ +module: nxos_devicealias +short_description: Configuration of device alias for Cisco NXOS MDS Switches. +description: +- Configuration of device alias for Cisco MDS NXOS. +version_added: 1.0.0 +author: +- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com) +notes: +- Tested against Cisco MDS NX-OS 8.4(1) +options: + distribute: + description: + - Enable/Disable device-alias distribution + type: bool + mode: + description: + - Mode of devices-alias, basic or enhanced + choices: + - basic + - enhanced + type: str + da: + description: + - List of device-alias to be added or removed + type: list + elements: dict + suboptions: + name: + description: + - Name of the device-alias to be added or removed + required: true + type: str + pwwn: + description: + - pwwn to which the name needs to be associated with + type: str + remove: + description: + - Removes the device-alias if set to True + type: bool + default: false + rename: + description: + - List of device-alias to be renamed + type: list + elements: dict + suboptions: + old_name: + description: + - Old name of the device-alias that needs to be renamed + required: true + type: str + new_name: + description: + - New name of the device-alias + required: true + type: str +""" + +EXAMPLES = """ +- name: Test that device alias module works + cisco.nxos.nxos_devicealias: + da: + - name: test1_add + pwwn: 56:2:22:11:22:88:11:67 + - name: test2_add + pwwn: 65:22:22:11:22:22:11:d + - name: dev1 + remove: true + - name: dev2 + remove: true + distribute: true + mode: enhanced + rename: + - new_name: bcd + old_name: abc + - new_name: bcd1 + old_name: abc1 + + +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: + - terminal dont-ask + - device-alias database + - device-alias name somename pwwn 10:00:00:00:89:a1:01:03 + - device-alias name somename1 pwwn 10:00:00:00:89:a1:02:03 + - device-alias commit + - no terminal dont-ask +""" + +import string + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +__metaclass__ = type + +VALID_DA_CHARS = ("-", "_", "$", "^") + + +class showDeviceAliasStatus(object): + """docstring for showDeviceAliasStatus""" + + def __init__(self, module): + self.module = module + self.distribute = "" + self.mode = "" + self.locked = False + self.update() + + def execute_show_cmd(self, cmd): + output = execute_show_command(cmd, self.module)[0] + return output + + def update(self): + command = "show device-alias status" + output = self.execute_show_cmd(command).split("\n") + for o in output: + if "Fabric Distribution" in o: + self.distribute = o.split(":")[1].strip().lower() + if "Mode" in o: + self.mode = o.split("Mode:")[1].strip().lower() + if "Locked" in o: + self.locked = True + + def isLocked(self): + return self.locked + + def getDistribute(self): + return self.distribute + + def getMode(self): + return self.mode + + +class showDeviceAliasDatabase(object): + """docstring for showDeviceAliasDatabase""" + + def __init__(self, module): + self.module = module + self.da_dict = {} + self.update() + + def execute_show_cmd(self, cmd): + output = execute_show_command(cmd, self.module)[0] + return output + + def update(self): + command = "show device-alias database" + # output = execute_show_command(command, self.module)[0].split("\n") + output = self.execute_show_cmd(command) + self.da_list = output.split("\n") + for eachline in self.da_list: + if "device-alias" in eachline: + sv = eachline.strip().split() + self.da_dict[sv[2]] = sv[4] + + def isNameInDaDatabase(self, name): + return name in self.da_dict.keys() + + def isPwwnInDaDatabase(self, pwwn): + newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")]) + return newpwwn in self.da_dict.values() + + def isNamePwwnPresentInDatabase(self, name, pwwn): + newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")]) + if name in self.da_dict.keys(): + if newpwwn == self.da_dict[name]: + return True + return False + + def getPwwnByName(self, name): + if name in self.da_dict.keys(): + return self.da_dict[name] + else: + return None + + def getNameByPwwn(self, pwwn): + newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")]) + for n, p in self.da_dict.items(): + if p == newpwwn: + return n + return None + + +def isPwwnValid(pwwn): + pwwnsplit = pwwn.split(":") + if len(pwwnsplit) != 8: + return False + for eachpwwnsplit in pwwnsplit: + if len(eachpwwnsplit) > 2 or len(eachpwwnsplit) < 1: + return False + if not all(c in string.hexdigits for c in eachpwwnsplit): + return False + return True + + +def isNameValid(name): + if not name[0].isalpha(): + # Illegal first character. Name must start with a letter + return False + if len(name) > 64: + return False + for character in name: + if not character.isalnum() and character not in VALID_DA_CHARS: + return False + return True + + +def execute_show_command(command, module, command_type="cli_show"): + output = "text" + commands = [{"command": command, "output": output}] + out = run_commands(module, commands) + return out + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def main(): + element_spec = dict( + name=dict(required=True, type="str"), + pwwn=dict(type="str"), + remove=dict(type="bool", default=False), + ) + + element_spec_rename = dict( + old_name=dict(required=True, type="str"), + new_name=dict(required=True, type="str"), + ) + + argument_spec = dict( + distribute=dict(type="bool"), + mode=dict(type="str", choices=["enhanced", "basic"]), + da=dict(type="list", elements="dict", options=element_spec), + rename=dict(type="list", elements="dict", options=element_spec_rename), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + messages = list() + commands_to_execute = list() + result = {"changed": False} + + distribute = module.params["distribute"] + mode = module.params["mode"] + da = module.params["da"] + rename = module.params["rename"] + + # Step 0.0: Validate syntax of name and pwwn + # Also validate syntax of rename arguments + if da is not None: + for eachdict in da: + name = eachdict["name"] + pwwn = eachdict["pwwn"] + remove = eachdict["remove"] + if pwwn is not None: + pwwn = pwwn.lower() + if not remove: + if pwwn is None: + module.fail_json( + msg="This device alias name " + + str(name) + + " which needs to be added, does not have pwwn specified. Please specify a valid pwwn", + ) + if not isNameValid(name): + module.fail_json( + msg="This pwwn name is invalid : " + + str(name) + + ". Note that name cannot be more than 64 alphanumeric chars, " + + "it must start with a letter, and can only contain these characters: " + + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]), + ) + if not isPwwnValid(pwwn): + module.fail_json( + msg="This pwwn is invalid : " + + str(pwwn) + + ". Please check that its a valid pwwn", + ) + if rename is not None: + for eachdict in rename: + oldname = eachdict["old_name"] + newname = eachdict["new_name"] + if not isNameValid(oldname): + module.fail_json( + msg="This pwwn name is invalid : " + + str(oldname) + + ". Note that name cannot be more than 64 alphanumeric chars, " + + "it must start with a letter, and can only contain these characters: " + + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]), + ) + if not isNameValid(newname): + module.fail_json( + msg="This pwwn name is invalid : " + + str(newname) + + ". Note that name cannot be more than 64 alphanumeric chars, " + + "it must start with a letter, and can only contain these characters: " + + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]), + ) + + # Step 0.1: Check DA status + shDAStausObj = showDeviceAliasStatus(module) + d = shDAStausObj.getDistribute() + m = shDAStausObj.getMode() + if shDAStausObj.isLocked(): + module.fail_json(msg="device-alias has acquired lock on the switch. Hence cannot procced.") + + # Step 1: Process distribute + commands = [] + if distribute is not None: + if distribute: + # playbook has distribute as True(enabled) + if d == "disabled": + # but switch distribute is disabled(false), so set it to + # true(enabled) + commands.append("device-alias distribute") + messages.append("device-alias distribute changed from disabled to enabled") + else: + messages.append( + "device-alias distribute remains unchanged. current distribution mode is enabled", + ) + else: + # playbook has distribute as False(disabled) + if d == "enabled": + # but switch distribute is enabled(true), so set it to + # false(disabled) + commands.append("no device-alias distribute") + messages.append("device-alias distribute changed from enabled to disabled") + else: + messages.append( + "device-alias distribute remains unchanged. current distribution mode is disabled", + ) + + cmds = flatten_list(commands) + if cmds: + commands_to_execute = commands_to_execute + cmds + if module.check_mode: + # Check mode implemented at the da_add/da_remove stage + pass + else: + result["changed"] = True + load_config(module, cmds) + + # Step 2: Process mode + commands = [] + if mode is not None: + if mode == "basic": + # playbook has mode as basic + if m == "enhanced": + # but switch mode is enhanced, so set it to basic + commands.append("no device-alias mode enhanced") + messages.append("device-alias mode changed from enhanced to basic") + else: + messages.append("device-alias mode remains unchanged. current mode is basic") + + else: + # playbook has mode as enhanced + if m == "basic": + # but switch mode is basic, so set it to enhanced + commands.append("device-alias mode enhanced") + messages.append("device-alias mode changed from basic to enhanced") + else: + messages.append("device-alias mode remains unchanged. current mode is enhanced") + + if commands: + if distribute: + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + else: + if distribute is None and d == "enabled": + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + + cmds = flatten_list(commands) + + if cmds: + commands_to_execute = commands_to_execute + cmds + if module.check_mode: + # Check mode implemented at the end + pass + else: + result["changed"] = True + load_config(module, cmds) + + # Step 3: Process da + commands = [] + shDADatabaseObj = showDeviceAliasDatabase(module) + if da is not None: + da_remove_list = [] + da_add_list = [] + for eachdict in da: + name = eachdict["name"] + pwwn = eachdict["pwwn"] + remove = eachdict["remove"] + if pwwn is not None: + pwwn = pwwn.lower() + if remove: + if shDADatabaseObj.isNameInDaDatabase(name): + commands.append("no device-alias name " + name) + da_remove_list.append(name) + else: + messages.append( + name + + " - This device alias name is not in switch device-alias database, hence cannot be removed.", + ) + else: + if shDADatabaseObj.isNamePwwnPresentInDatabase(name, pwwn): + messages.append( + name + + " : " + + pwwn + + " - This device alias name,pwwn is already in switch device-alias database, hence nothing to configure", + ) + else: + if shDADatabaseObj.isNameInDaDatabase(name): + module.fail_json( + msg=name + + " - This device alias name is already present in switch device-alias database but assigned to another pwwn (" + + shDADatabaseObj.getPwwnByName(name) + + ") hence cannot be added", + ) + + elif shDADatabaseObj.isPwwnInDaDatabase(pwwn): + module.fail_json( + msg=pwwn + + " - This device alias pwwn is already present in switch device-alias database but assigned to another name (" + + shDADatabaseObj.getNameByPwwn(pwwn) + + ") hence cannot be added", + ) + + else: + commands.append("device-alias name " + name + " pwwn " + pwwn) + da_add_list.append(name) + + if len(da_add_list) != 0 or len(da_remove_list) != 0: + commands = ["device-alias database"] + commands + if distribute: + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + else: + if distribute is None and d == "enabled": + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + + cmds = flatten_list(commands) + if cmds: + commands_to_execute = commands_to_execute + cmds + if module.check_mode: + # Check mode implemented at the end + pass + else: + result["changed"] = True + load_config(module, cmds) + if len(da_remove_list) != 0: + messages.append( + "the required device-alias were removed. " + ",".join(da_remove_list), + ) + if len(da_add_list) != 0: + messages.append( + "the required device-alias were added. " + ",".join(da_add_list), + ) + + # Step 5: Process rename + commands = [] + if rename is not None: + for eachdict in rename: + oldname = eachdict["old_name"] + newname = eachdict["new_name"] + if shDADatabaseObj.isNameInDaDatabase(newname): + module.fail_json( + changed=False, + commands=cmds, + msg=newname + + " - this name is already present in the device-alias database, hence we cannot rename " + + oldname + + " with this one", + ) + if shDADatabaseObj.isNameInDaDatabase(oldname): + commands.append("device-alias rename " + oldname + " " + newname) + else: + module.fail_json( + changed=False, + commands=cmds, + msg=oldname + + " - this name is not present in the device-alias database, hence we cannot rename.", + ) + + if len(commands) != 0: + commands = ["device-alias database"] + commands + if distribute: + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + else: + if distribute is None and d == "enabled": + commands.append("device-alias commit") + commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"] + cmds = flatten_list(commands) + if cmds: + commands_to_execute = commands_to_execute + cmds + if module.check_mode: + # Check mode implemented at the end + pass + else: + result["changed"] = True + load_config(module, cmds) + + # Step END: check for 'check' mode + if module.check_mode: + module.exit_json( + changed=False, + commands=commands_to_execute, + msg="Check Mode: No cmds issued to the hosts", + ) + + result["messages"] = messages + result["commands"] = commands_to_execute + result["warnings"] = warnings + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py new file mode 100644 index 00000000..d95d95a9 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py @@ -0,0 +1,354 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or +# https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +DOCUMENTATION = """ +module: nxos_vsan +short_description: Configuration of vsan for Cisco NXOS MDS Switches. +description: +- Configuration of vsan for Cisco MDS NXOS. +version_added: 1.0.0 +author: +- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com) +notes: +- Tested against Cisco MDS NX-OS 8.4(1) +options: + vsan: + description: + - List of vsan details to be added or removed + type: list + elements: dict + suboptions: + id: + description: + - Vsan id + required: true + type: int + name: + description: + - Name of the vsan + type: str + suspend: + description: + - suspend the vsan if True + type: bool + remove: + description: + - Removes the vsan if True + type: bool + interface: + description: + - List of vsan's interfaces to be added + type: list + elements: str +""" + +EXAMPLES = """ +- name: Test that vsan module works + cisco.nxos.nxos_vsan: + vsan: + - id: 922 + interface: + - fc1/1 + - fc1/2 + - port-channel 1 + name: vsan-SAN-A + remove: false + suspend: false + - id: 923 + interface: + - fc1/11 + - fc1/21 + - port-channel 2 + name: vsan-SAN-B + remove: false + suspend: true + - id: 1923 + name: vsan-SAN-Old + remove: true +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: + - terminal dont-ask + - vsan database + - vsan 922 interface fc1/40 + - vsan 922 interface port-channel 155 + - no terminal dont-ask +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +__metaclass__ = type + + +class Vsan(object): + def __init__(self, vsanid): + self.vsanid = vsanid + self.vsanname = None + self.vsanstate = None + self.vsanoperstate = None + self.vsaninterfaces = [] + + +class GetVsanInfoFromSwitch(object): + """docstring for GetVsanInfoFromSwitch""" + + def __init__(self, module): + self.module = module + self.vsaninfo = {} + self.processShowVsan() + self.processShowVsanMembership() + + def execute_show_vsan_cmd(self): + output = execute_show_command("show vsan", self.module)[0] + return output + + def execute_show_vsan_mem_cmd(self): + output = execute_show_command("show vsan membership", self.module)[0] + return output + + def processShowVsan(self): + patv = r"^vsan\s+(\d+)\s+information" + patnamestate = "name:(.*)state:(.*)" + patoperstate = "operational state:(.*)" + + output = self.execute_show_vsan_cmd().split("\n") + for o in output: + z = re.match(patv, o.strip()) + if z: + v = z.group(1).strip() + self.vsaninfo[v] = Vsan(v) + + z1 = re.match(patnamestate, o.strip()) + if z1: + n = z1.group(1).strip() + s = z1.group(2).strip() + self.vsaninfo[v].vsanname = n + self.vsaninfo[v].vsanstate = s + + z2 = re.match(patoperstate, o.strip()) + if z2: + oper = z2.group(1).strip() + self.vsaninfo[v].vsanoperstate = oper + + # 4094/4079 vsan is always present + self.vsaninfo["4079"] = Vsan("4079") + self.vsaninfo["4094"] = Vsan("4094") + + def processShowVsanMembership(self): + patv = r"^vsan\s+(\d+).*" + output = self.execute_show_vsan_mem_cmd().split("\n") + memlist = [] + v = None + for o in output: + z = re.match(patv, o.strip()) + if z: + if v is not None: + self.vsaninfo[v].vsaninterfaces = memlist + memlist = [] + v = z.group(1) + if "interfaces" not in o: + llist = o.strip().split() + memlist = memlist + llist + self.vsaninfo[v].vsaninterfaces = memlist + + def getVsanInfoObjects(self): + return self.vsaninfo + + +def execute_show_command(command, module, command_type="cli_show"): + output = "text" + commands = [{"command": command, "output": output}] + return run_commands(module, commands) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def main(): + vsan_element_spec = dict( + id=dict(required=True, type="int"), + name=dict(type="str"), + remove=dict(type="bool"), + suspend=dict(type="bool"), + interface=dict(type="list", elements="str"), + ) + + argument_spec = dict(vsan=dict(type="list", elements="dict", options=vsan_element_spec)) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + warnings = list() + messages = list() + commands_executed = list() + result = {"changed": False} + + obj = GetVsanInfoFromSwitch(module) + dictSwVsanObjs = obj.getVsanInfoObjects() + + commands = [] + vsan_list = module.params["vsan"] + + for eachvsan in vsan_list: + vsanid = str(eachvsan["id"]) + vsanname = eachvsan["name"] + vsanremove = eachvsan["remove"] + vsansuspend = eachvsan["suspend"] + vsaninterface_list = eachvsan["interface"] + + if int(vsanid) < 1 or int(vsanid) >= 4095: + module.fail_json( + msg=vsanid + " - This is an invalid vsan. Supported vsan range is 1-4094", + ) + + if vsanid in dictSwVsanObjs.keys(): + sw_vsanid = vsanid + sw_vsanname = dictSwVsanObjs[vsanid].vsanname + sw_vsanstate = dictSwVsanObjs[vsanid].vsanstate + sw_vsaninterfaces = dictSwVsanObjs[vsanid].vsaninterfaces + else: + sw_vsanid = None + sw_vsanname = None + sw_vsanstate = None + sw_vsaninterfaces = [] + + if vsanremove: + # Negative case: + if vsanid == "4079" or vsanid == "4094": + messages.append(str(vsanid) + " is a reserved vsan, hence cannot be removed") + continue + if vsanid == sw_vsanid: + commands.append("no vsan " + str(vsanid)) + messages.append("deleting the vsan " + str(vsanid)) + else: + messages.append( + "There is no vsan " + + str(vsanid) + + " present in the switch. Hence there is nothing to delete", + ) + continue + else: + # Negative case: + if vsanid == "4079" or vsanid == "4094": + messages.append( + str(vsanid) + " is a reserved vsan, and always present on the switch", + ) + else: + if vsanid == sw_vsanid: + messages.append( + "There is already a vsan " + + str(vsanid) + + " present in the switch. Hence there is nothing to configure", + ) + else: + commands.append("vsan " + str(vsanid)) + messages.append("creating vsan " + str(vsanid)) + + if vsanname is not None: + # Negative case: + if vsanid == "4079" or vsanid == "4094": + messages.append(str(vsanid) + " is a reserved vsan, and cannot be renamed") + else: + if vsanname == sw_vsanname: + messages.append( + "There is already a vsan " + + str(vsanid) + + " present in the switch, which has the name " + + vsanname + + " Hence there is nothing to configure", + ) + else: + commands.append("vsan " + str(vsanid) + " name " + vsanname) + messages.append("setting vsan name to " + vsanname + " for vsan " + str(vsanid)) + + if vsansuspend: + # Negative case: + if vsanid == "4079" or vsanid == "4094": + messages.append(str(vsanid) + " is a reserved vsan, and cannot be suspended") + else: + if sw_vsanstate == "suspended": + messages.append( + "There is already a vsan " + + str(vsanid) + + " present in the switch, which is in suspended state ", + ) + else: + commands.append("vsan " + str(vsanid) + " suspend") + messages.append("suspending the vsan " + str(vsanid)) + else: + if sw_vsanstate == "active": + messages.append( + "There is already a vsan " + + str(vsanid) + + " present in the switch, which is in active state ", + ) + else: + commands.append("no vsan " + str(vsanid) + " suspend") + messages.append("no suspending the vsan " + str(vsanid)) + + if vsaninterface_list is not None: + for each_interface_name in vsaninterface_list: + # For fcip,port-channel,vfc-port-channel need to remove the + # extra space to compare + temp = re.sub(" +", "", each_interface_name) + if temp in sw_vsaninterfaces: + messages.append( + each_interface_name + + " is already present in the vsan " + + str(vsanid) + + " interface list", + ) + else: + commands.append("vsan " + str(vsanid) + " interface " + each_interface_name) + messages.append( + "adding interface " + each_interface_name + " to vsan " + str(vsanid), + ) + + if len(commands) != 0: + commands = ["terminal dont-ask"] + ["vsan database"] + commands + ["no terminal dont-ask"] + + cmds = flatten_list(commands) + commands_executed = cmds + + if commands_executed: + if module.check_mode: + module.exit_json( + changed=False, + commands=commands_executed, + msg="Check Mode: No cmds issued to the hosts", + ) + else: + result["changed"] = True + load_config(module, commands_executed) + + result["messages"] = messages + result["commands"] = commands_executed + result["warnings"] = warnings + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py new file mode 100644 index 00000000..7c9fba30 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py @@ -0,0 +1,888 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or +# https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + + +DOCUMENTATION = """ +module: nxos_zone_zoneset +short_description: Configuration of zone/zoneset for Cisco NXOS MDS Switches. +description: +- Configuration of zone/zoneset for Cisco MDS NXOS. +version_added: 1.0.0 +author: +- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com) +notes: +- Tested against Cisco MDS NX-OS 8.4(1) +options: + zone_zoneset_details: + description: + - List of zone/zoneset details to be added or removed + type: list + elements: dict + suboptions: + vsan: + description: + - vsan id + required: true + type: int + mode: + description: + - mode of the zone for the vsan + choices: + - enhanced + - basic + type: str + default_zone: + description: + - default zone behaviour for the vsan + choices: + - permit + - deny + type: str + smart_zoning: + description: + - Removes the vsan if True + type: bool + zone: + description: + - List of zone options for that vsan + type: list + elements: dict + suboptions: + name: + description: + - name of the zone + required: true + type: str + remove: + description: + - Deletes the zone if True + type: bool + default: false + members: + description: + - Members of the zone that needs to be removed or added + type: list + elements: dict + suboptions: + pwwn: + description: + - pwwn member of the zone, use alias 'device_alias' as option for + device_alias member + aliases: + - device_alias + required: true + type: str + remove: + description: + - Removes member from the zone if True + type: bool + default: false + devtype: + description: + - devtype of the zone member used along with Smart zoning config + choices: + - initiator + - target + - both + type: str + zoneset: + description: + - List of zoneset options for the vsan + type: list + elements: dict + suboptions: + name: + description: + - name of the zoneset + required: true + type: str + remove: + description: + - Removes zoneset if True + type: bool + default: false + action: + description: + - activates/de-activates the zoneset + choices: + - activate + - deactivate + type: str + members: + description: + - Members of the zoneset that needs to be removed or added + type: list + elements: dict + suboptions: + name: + description: + - name of the zone that needs to be added to the zoneset or removed + from the zoneset + required: true + type: str + remove: + description: + - Removes zone member from the zoneset + type: bool + default: false +""" + +EXAMPLES = """ +- name: Test that zone/zoneset module works + cisco.nxos.nxos_zone_zoneset: + zone_zoneset_details: + - mode: enhanced + vsan: 22 + zone: + - members: + - pwwn: 11:11:11:11:11:11:11:11 + - device_alias: test123 + - pwwn: 61:61:62:62:12:12:12:12 + remove: true + name: zoneA + - members: + - pwwn: 10:11:11:11:11:11:11:11 + - pwwn: 62:62:62:62:21:21:21:21 + name: zoneB + - name: zoneC + remove: true + zoneset: + - action: activate + members: + - name: zoneA + - name: zoneB + - name: zoneC + remove: true + name: zsetname1 + - action: deactivate + name: zsetTestExtra + remove: true + - mode: basic + smart_zoning: true + vsan: 21 + zone: + - members: + - devtype: both + pwwn: 11:11:11:11:11:11:11:11 + - pwwn: 62:62:62:62:12:12:12:12 + - devtype: both + pwwn: 92:62:62:62:12:12:1a:1a + remove: true + name: zone21A + - members: + - pwwn: 10:11:11:11:11:11:11:11 + - pwwn: 62:62:62:62:21:21:21:21 + name: zone21B + zoneset: + - action: activate + members: + - name: zone21A + - name: zone21B + name: zsetname212 + +""" + +RETURN = """ +commands: + description: commands sent to the device + returned: always + type: list + sample: + - terminal dont-ask + - zone name zoneA vsan 923 + - member pwwn 11:11:11:11:11:11:11:11 + - no member device-alias test123 + - zone commit vsan 923 + - no terminal dont-ask +messages: + description: debug messages + returned: always + type: list + sample: + - "zone mode is already enhanced ,no change in zone mode configuration for vsan 922" + - "zone member '11:11:11:11:11:11:11:11' is already present in zone 'zoneA' in vsan 922 hence nothing to add" + - "zone member 'test123' is already present in zone 'zoneA' in vsan 922 hence nothing to add" + - "zone member '61:61:62:62:12:12:12:12' is not present in zone 'zoneA' in vsan 922 hence nothing to remove" + - "zone member '10:11:11:11:11:11:11:11' is already present in zone 'zoneB' in vsan 922 hence nothing to add" + - "zone member '62:62:62:62:21:21:21:21' is already present in zone 'zoneB' in vsan 922 hence nothing to add" + - "zone 'zoneC' is not present in vsan 922 , so nothing to remove" +""" + + +import re + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + load_config, + run_commands, +) + + +__metaclass__ = type + + +class ShowZonesetActive(object): + """docstring for ShowZonesetActive""" + + def __init__(self, module, vsan): + self.vsan = vsan + self.module = module + self.activeZSName = None + self.parseCmdOutput() + + def execute_show_zoneset_active_cmd(self): + command = "show zoneset active vsan " + str(self.vsan) + " | grep zoneset" + output = execute_show_command(command, self.module)[0] + return output + + def parseCmdOutput(self): + patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan) + output = self.execute_show_zoneset_active_cmd().split("\n") + if len(output) == 0: + return + else: + for line in output: + line = line.strip() + mzs = re.match(patZoneset, line.strip()) + if mzs: + self.activeZSName = mzs.group(1).strip() + return + + def isZonesetActive(self, zsname): + if zsname == self.activeZSName: + return True + return False + + +class ShowZoneset(object): + """docstring for ShowZoneset""" + + def __init__(self, module, vsan): + self.vsan = vsan + self.module = module + self.zsDetails = {} + self.parseCmdOutput() + + def execute_show_zoneset_cmd(self): + command = "show zoneset vsan " + str(self.vsan) + output = execute_show_command(command, self.module)[0] + return output + + def parseCmdOutput(self): + patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan) + patZone = r"zone name (\S+) vsan " + str(self.vsan) + output = self.execute_show_zoneset_cmd().split("\n") + for line in output: + line = line.strip() + mzs = re.match(patZoneset, line.strip()) + mz = re.match(patZone, line.strip()) + if mzs: + zonesetname = mzs.group(1).strip() + self.zsDetails[zonesetname] = [] + continue + elif mz: + zonename = mz.group(1).strip() + v = self.zsDetails[zonesetname] + v.append(zonename) + self.zsDetails[zonesetname] = v + + def isZonesetPresent(self, zsname): + return zsname in self.zsDetails.keys() + + def isZonePresentInZoneset(self, zsname, zname): + if zsname in self.zsDetails.keys(): + return zname in self.zsDetails[zsname] + return False + + +class ShowZone(object): + """docstring for ShowZone""" + + def __init__(self, module, vsan): + self.vsan = vsan + self.module = module + self.zDetails = {} + self.parseCmdOutput() + + def execute_show_zone_vsan_cmd(self): + command = "show zone vsan " + str(self.vsan) + output = execute_show_command(command, self.module)[0] + return output + + def parseCmdOutput(self): + patZone = r"zone name (\S+) vsan " + str(self.vsan) + output = self.execute_show_zone_vsan_cmd().split("\n") + for line in output: + line = re.sub(r"[\[].*?[\]]", "", line) + line = " ".join(line.strip().split()) + if "init" in line: + line = line.replace("init", "initiator") + m = re.match(patZone, line) + if m: + zonename = m.group(1).strip() + self.zDetails[zonename] = [] + continue + else: + # For now we support only pwwn and device-alias under zone + # Ideally should use 'supported_choices'....but maybe next + # time. + if "pwwn" in line or "device-alias" in line: + v = self.zDetails[zonename] + v.append(line) + self.zDetails[zonename] = v + + def isZonePresent(self, zname): + return zname in self.zDetails.keys() + + def isZoneMemberPresent(self, zname, cmd): + if zname in self.zDetails.keys(): + zonememlist = self.zDetails[zname] + for eachline in zonememlist: + if cmd in eachline: + return True + return False + + def get_zDetails(self): + return self.zDetails + + +class ShowZoneStatus(object): + """docstring for ShowZoneStatus""" + + def __init__(self, module, vsan): + self.vsan = vsan + self.vsanAbsent = False + self.module = module + self.default_zone = "" + self.mode = "" + self.session = "" + self.sz = "" + self.locked = False + self.update() + + def execute_show_zone_status_cmd(self): + command = "show zone status vsan " + str(self.vsan) + output = execute_show_command(command, self.module)[0] + return output + + def update(self): + output = self.execute_show_zone_status_cmd().split("\n") + + patfordefzone = "VSAN: " + str(self.vsan) + r" default-zone:\s+(\S+).*" + patformode = r".*mode:\s+(\S+).*" + patforsession = r"^session:\s+(\S+).*" + patforsz = r".*smart-zoning:\s+(\S+).*" + for line in output: + if "is not configured" in line: + self.vsanAbsent = True + break + mdefz = re.match(patfordefzone, line.strip()) + mmode = re.match(patformode, line.strip()) + msession = re.match(patforsession, line.strip()) + msz = re.match(patforsz, line.strip()) + + if mdefz: + self.default_zone = mdefz.group(1) + if mmode: + self.mode = mmode.group(1) + if msession: + self.session = msession.group(1) + if self.session != "none": + self.locked = True + if msz: + self.sz = msz.group(1) + + def isLocked(self): + return self.locked + + def getDefaultZone(self): + return self.default_zone + + def getMode(self): + return self.mode + + def getSmartZoningStatus(self): + return self.sz + + def isVsanAbsent(self): + return self.vsanAbsent + + +def execute_show_command(command, module, command_type="cli_show"): + output = "text" + commands = [{"command": command, "output": output}] + return run_commands(module, commands) + + +def flatten_list(command_lists): + flat_command_list = [] + for command in command_lists: + if isinstance(command, list): + flat_command_list.extend(command) + else: + flat_command_list.append(command) + return flat_command_list + + +def getMemType(supported_choices, allmemkeys, default="pwwn"): + for eachchoice in supported_choices: + if eachchoice in allmemkeys: + return eachchoice + return default + + +def main(): + supported_choices = ["device_alias"] + zone_member_spec = dict( + pwwn=dict(required=True, type="str", aliases=["device_alias"]), + devtype=dict(type="str", choices=["initiator", "target", "both"]), + remove=dict(type="bool", default=False), + ) + + zone_spec = dict( + name=dict(required=True, type="str"), + members=dict(type="list", elements="dict", options=zone_member_spec), + remove=dict(type="bool", default=False), + ) + + zoneset_member_spec = dict( + name=dict(required=True, type="str"), + remove=dict(type="bool", default=False), + ) + + zoneset_spec = dict( + name=dict(type="str", required=True), + members=dict(type="list", elements="dict", options=zoneset_member_spec), + remove=dict(type="bool", default=False), + action=dict(type="str", choices=["activate", "deactivate"]), + ) + + zonedetails_spec = dict( + vsan=dict(required=True, type="int"), + mode=dict(type="str", choices=["enhanced", "basic"]), + default_zone=dict(type="str", choices=["permit", "deny"]), + smart_zoning=dict(type="bool"), + zone=dict(type="list", elements="dict", options=zone_spec), + zoneset=dict(type="list", elements="dict", options=zoneset_spec), + ) + + argument_spec = dict( + zone_zoneset_details=dict(type="list", elements="dict", options=zonedetails_spec), + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + warnings = list() + messages = list() + commands = list() + result = {"changed": False} + + commands_executed = [] + listOfZoneDetails = module.params["zone_zoneset_details"] + for eachZoneZonesetDetail in listOfZoneDetails: + vsan = eachZoneZonesetDetail["vsan"] + op_mode = eachZoneZonesetDetail["mode"] + op_default_zone = eachZoneZonesetDetail["default_zone"] + op_smart_zoning = eachZoneZonesetDetail["smart_zoning"] + op_zone = eachZoneZonesetDetail["zone"] + op_zoneset = eachZoneZonesetDetail["zoneset"] + + # Step1: execute show zone status and get + shZoneStatusObj = ShowZoneStatus(module, vsan) + sw_default_zone = shZoneStatusObj.getDefaultZone() + sw_mode = shZoneStatusObj.getMode() + sw_smart_zoning = shZoneStatusObj.getSmartZoningStatus() + + if sw_smart_zoning.lower() == "Enabled".lower(): + sw_smart_zoning_bool = True + else: + sw_smart_zoning_bool = False + + if shZoneStatusObj.isVsanAbsent(): + module.fail_json( + msg="Vsan " + str(vsan) + " is not present in the switch. Hence cannot procced.", + ) + + if shZoneStatusObj.isLocked(): + module.fail_json( + msg="zone has acquired lock on the switch for vsan " + + str(vsan) + + ". Hence cannot procced.", + ) + + # Process zone default zone options + if op_default_zone is not None: + if op_default_zone != sw_default_zone: + if op_default_zone == "permit": + commands_executed.append("zone default-zone permit vsan " + str(vsan)) + messages.append( + "default zone configuration changed from deny to permit for vsan " + + str(vsan), + ) + else: + commands_executed.append("no zone default-zone permit vsan " + str(vsan)) + messages.append( + "default zone configuration changed from permit to deny for vsan " + + str(vsan), + ) + else: + messages.append( + "default zone is already " + + op_default_zone + + " ,no change in default zone configuration for vsan " + + str(vsan), + ) + + # Process zone mode options + if op_mode is not None: + if op_mode != sw_mode: + if op_mode == "enhanced": + commands_executed.append("zone mode enhanced vsan " + str(vsan)) + messages.append( + "zone mode configuration changed from basic to enhanced for vsan " + + str(vsan), + ) + else: + commands_executed.append("no zone mode enhanced vsan " + str(vsan)) + messages.append( + "zone mode configuration changed from enhanced to basic for vsan " + + str(vsan), + ) + else: + messages.append( + "zone mode is already " + + op_mode + + " ,no change in zone mode configuration for vsan " + + str(vsan), + ) + + # Process zone smart-zone options + if op_smart_zoning is not None: + if op_smart_zoning != sw_smart_zoning_bool: + if op_smart_zoning: + commands_executed.append("zone smart-zoning enable vsan " + str(vsan)) + messages.append("smart-zoning enabled for vsan " + str(vsan)) + else: + commands_executed.append("no zone smart-zoning enable vsan " + str(vsan)) + messages.append("smart-zoning disabled for vsan " + str(vsan)) + else: + messages.append( + "smart-zoning is already set to " + + sw_smart_zoning + + " , no change in smart-zoning configuration for vsan " + + str(vsan), + ) + + # Process zone member options + # TODO: Obviously this needs to be cleaned up properly, as there are a lot of ifelse statements which is bad + # Will take it up later becoz of time constraints + if op_zone is not None: + shZoneObj = ShowZone(module, vsan) + for eachzone in op_zone: + zname = eachzone["name"] + zmembers = eachzone["members"] + removeflag = eachzone["remove"] + if removeflag: + if shZoneObj.isZonePresent(zname): + messages.append("zone '" + zname + "' is removed from vsan " + str(vsan)) + commands_executed.append("no zone name " + zname + " vsan " + str(vsan)) + else: + messages.append( + "zone '" + + zname + + "' is not present in vsan " + + str(vsan) + + " , so nothing to remove", + ) + else: + if zmembers is None: + if shZoneObj.isZonePresent(zname): + messages.append( + "zone '" + zname + "' is already present in vsan " + str(vsan), + ) + else: + commands_executed.append("zone name " + zname + " vsan " + str(vsan)) + messages.append("zone '" + zname + "' is created in vsan " + str(vsan)) + else: + cmdmemlist = [] + for eachmem in zmembers: + memtype = getMemType(supported_choices, eachmem.keys()) + cmd = memtype.replace("_", "-") + " " + eachmem[memtype] + if op_smart_zoning or sw_smart_zoning_bool: + if eachmem["devtype"] is not None: + cmd = cmd + " " + eachmem["devtype"] + if eachmem["remove"]: + if shZoneObj.isZonePresent(zname): + if shZoneObj.isZoneMemberPresent(zname, cmd): + cmd = "no member " + cmd + cmdmemlist.append(cmd) + if op_smart_zoning and eachmem["devtype"] is not None: + messages.append( + "removing zone member '" + + eachmem[memtype] + + " of device type '" + + eachmem["devtype"] + + "' from zone '" + + zname + + "' in vsan " + + str(vsan), + ) + else: + messages.append( + "removing zone member '" + + eachmem[memtype] + + "' from zone '" + + zname + + "' in vsan " + + str(vsan), + ) + else: + if op_smart_zoning and eachmem["devtype"] is not None: + messages.append( + "zone member '" + + eachmem[memtype] + + "' of device type '" + + eachmem["devtype"] + + "' is not present in zone '" + + zname + + "' in vsan " + + str(vsan) + + " hence nothing to remove", + ) + else: + messages.append( + "zone member '" + + eachmem[memtype] + + "' is not present in zone '" + + zname + + "' in vsan " + + str(vsan) + + " hence nothing to remove", + ) + else: + messages.append( + "zone '" + + zname + + "' is not present in vsan " + + str(vsan) + + " , hence cannot remove the members", + ) + + else: + if shZoneObj.isZoneMemberPresent(zname, cmd): + if op_smart_zoning and eachmem["devtype"] is not None: + messages.append( + "zone member '" + + eachmem[memtype] + + "' of device type '" + + eachmem["devtype"] + + "' is already present in zone '" + + zname + + "' in vsan " + + str(vsan) + + " hence nothing to add", + ) + else: + messages.append( + "zone member '" + + eachmem[memtype] + + "' is already present in zone '" + + zname + + "' in vsan " + + str(vsan) + + " hence nothing to add", + ) + else: + cmd = "member " + cmd + cmdmemlist.append(cmd) + if op_smart_zoning and eachmem["devtype"] is not None: + messages.append( + "adding zone member '" + + eachmem[memtype] + + "' of device type '" + + eachmem["devtype"] + + "' to zone '" + + zname + + "' in vsan " + + str(vsan), + ) + else: + messages.append( + "adding zone member '" + + eachmem[memtype] + + "' to zone '" + + zname + + "' in vsan " + + str(vsan), + ) + if len(cmdmemlist) != 0: + commands_executed.append("zone name " + zname + " vsan " + str(vsan)) + commands_executed = commands_executed + cmdmemlist + + # Process zoneset member options + if op_zoneset is not None: + dactcmd = [] + actcmd = [] + shZonesetObj = ShowZoneset(module, vsan) + shZonesetActiveObj = ShowZonesetActive(module, vsan) + for eachzoneset in op_zoneset: + zsetname = eachzoneset["name"] + zsetmembers = eachzoneset["members"] + removeflag = eachzoneset["remove"] + actionflag = eachzoneset["action"] + if removeflag: + if shZonesetObj.isZonesetPresent(zsetname): + messages.append( + "zoneset '" + zsetname + "' is removed from vsan " + str(vsan), + ) + commands_executed.append( + "no zoneset name " + zsetname + " vsan " + str(vsan), + ) + else: + messages.append( + "zoneset '" + + zsetname + + "' is not present in vsan " + + str(vsan) + + " ,hence there is nothing to remove", + ) + else: + if zsetmembers is not None: + cmdmemlist = [] + for eachzsmem in zsetmembers: + zsetmem_name = eachzsmem["name"] + zsetmem_removeflag = eachzsmem["remove"] + if zsetmem_removeflag: + if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name): + cmd = "no member " + zsetmem_name + cmdmemlist.append(cmd) + messages.append( + "removing zoneset member '" + + zsetmem_name + + "' from zoneset '" + + zsetname + + "' in vsan " + + str(vsan), + ) + else: + messages.append( + "zoneset member '" + + zsetmem_name + + "' is not present in zoneset '" + + zsetname + + "' in vsan " + + str(vsan) + + " ,hence there is nothing to remove", + ) + else: + if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name): + messages.append( + "zoneset member '" + + zsetmem_name + + "' is already present in zoneset '" + + zsetname + + "' in vsan " + + str(vsan) + + " ,hence there is nothing to add", + ) + else: + cmd = "member " + zsetmem_name + cmdmemlist.append(cmd) + messages.append( + "adding zoneset member '" + + zsetmem_name + + "' to zoneset '" + + zsetname + + "' in vsan " + + str(vsan), + ) + if len(cmdmemlist) != 0: + commands_executed.append( + "zoneset name " + zsetname + " vsan " + str(vsan), + ) + commands_executed = commands_executed + cmdmemlist + else: + if shZonesetObj.isZonesetPresent(zsetname): + messages.append( + "zoneset '" + + zsetname + + "' is already present in vsan " + + str(vsan), + ) + else: + commands_executed.append( + "zoneset name " + zsetname + " vsan " + str(vsan), + ) + messages.append( + "zoneset '" + zsetname + "' is created in vsan " + str(vsan), + ) + + # Process zoneset activate options + if actionflag == "deactivate": + if shZonesetActiveObj.isZonesetActive(zsetname): + messages.append( + "deactivating zoneset '" + zsetname + "' in vsan " + str(vsan), + ) + dactcmd.append( + "no zoneset activate name " + zsetname + " vsan " + str(vsan), + ) + else: + messages.append( + "zoneset '" + + zsetname + + "' in vsan " + + str(vsan) + + " is not activated, hence cannot deactivate", + ) + elif actionflag == "activate": + if commands_executed: + messages.append( + "activating zoneset '" + zsetname + "' in vsan " + str(vsan), + ) + actcmd.append("zoneset activate name " + zsetname + " vsan " + str(vsan)) + else: + messages.append( + "no changes to existing zoneset '" + + zsetname + + "' in vsan " + + str(vsan) + + " hence activate action is ignored", + ) + commands_executed = commands_executed + dactcmd + actcmd + + if commands_executed: + if op_mode == "enhanced": + commands_executed.append("zone commit vsan " + str(vsan)) + elif op_mode is None: + if sw_mode == "enhanced": + commands_executed.append("zone commit vsan " + str(vsan)) + + if commands_executed: + commands_executed = ["terminal dont-ask"] + commands_executed + ["no terminal dont-ask"] + + cmds = flatten_list(commands_executed) + if cmds: + if module.check_mode: + module.exit_json( + changed=False, + commands=cmds, + msg="Check Mode: No cmds issued to the hosts", + ) + else: + result["changed"] = True + commands = commands + cmds + load_config(module, cmds) + + result["messages"] = messages + result["commands"] = commands_executed + result["warnings"] = warnings + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/nxos/plugins/netconf/__init__.py b/ansible_collections/cisco/nxos/plugins/netconf/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/netconf/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/netconf/nxos.py b/ansible_collections/cisco/nxos/plugins/netconf/nxos.py new file mode 100644 index 00000000..fcbee795 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/netconf/nxos.py @@ -0,0 +1,46 @@ +# +# (c) 2021 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 = """ +author: Ansible Networking Team (@ansible-network) +name: nxos +short_description: Use nxos netconf plugin to run netconf commands on Cisco NX-OS platform. +description: +- This nxos plugin provides low level abstraction apis for sending and receiving + netconf commands from Cisco NX-OS network devices. +version_added: 2.3.0 +options: + ncclient_device_handler: + type: str + default: nexus + description: + - Specifies the ncclient device handler name for Cisco NX-OS network os. To + identify the ncclient device handler name refer ncclient library documentation. +""" + +from ansible.plugins.netconf import NetconfBase +from ansible_collections.ansible.netcommon.plugins.plugin_utils.netconf_base import NetconfBase + + +class Netconf(NetconfBase): + pass diff --git a/ansible_collections/cisco/nxos/plugins/terminal/__init__.py b/ansible_collections/cisco/nxos/plugins/terminal/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/terminal/__init__.py diff --git a/ansible_collections/cisco/nxos/plugins/terminal/nxos.py b/ansible_collections/cisco/nxos/plugins/terminal/nxos.py new file mode 100644 index 00000000..ff0ada40 --- /dev/null +++ b/ansible_collections/cisco/nxos/plugins/terminal/nxos.py @@ -0,0 +1,139 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.ansible.netcommon.plugins.plugin_utils.terminal_base import TerminalBase + + +class TerminalModule(TerminalBase): + terminal_stdout_re = [ + re.compile( + rb"[\r\n](?!\s*<)?(\x1b\S+)*[a-zA-Z_0-9]{1}[a-zA-Z0-9-_.]*[>|#](?:\s*)(\x1b\S+)*$", + ), + re.compile(rb"[\r\n]?[a-zA-Z0-9]{1}[a-zA-Z0-9-_.]*\(.+\)#(?:\s*)$"), + ] + + terminal_stderr_re = [ + re.compile(rb"% ?Error"), + re.compile(rb"\nerror:(.*)", re.I), + re.compile(rb"^% \w+", re.M), + re.compile(rb"% ?Bad secret"), + re.compile(rb"invalid input", re.I), + re.compile(rb"(?:incomplete|ambiguous) command", re.I), + re.compile(rb"connection timed out", re.I), + re.compile(rb"[^\r\n] not found", re.I), + re.compile(rb"'[^']' +returned error code: ?\d+"), + re.compile(rb"syntax error"), + re.compile(rb"unknown command"), + re.compile(rb"user not present"), + re.compile(rb"invalid (.+?)at '\^' marker", re.I), + re.compile(rb"configuration not allowed .+ at '\^' marker"), + re.compile( + rb"[B|b]aud rate of console should be.* (\d*) to increase [a-z]* level", + re.I, + ), + re.compile(rb"cannot apply non-existing acl policy to interface", re.I), + re.compile(rb"Duplicate sequence number", re.I), + re.compile( + rb"Cannot apply ACL to an interface that is a port-channel member", + re.I, + ), + re.compile(rb"No corresponding (.+) configured", re.I), + re.compile(rb"(.+)please specify sequence number", re.I), + ] + + terminal_config_prompt = re.compile(r"^.*\((?!maint-mode).*\)#$") + + def on_become(self, passwd=None): + if self._get_prompt().strip().endswith(b"enable#"): + return + + out = self._exec_cli_command("show privilege") + out = to_text(out, errors="surrogate_then_replace").strip() + + # if already at privilege level 15 return + if "15" in out: + return + + if self.validate_user_role(): + return + + if "Disabled" in out: + raise AnsibleConnectionFailure("Feature privilege is not enabled") + + cmd = {"command": "enable"} + if passwd: + cmd["prompt"] = to_text(r"(?i)[\r\n]?Password: $", errors="surrogate_or_strict") + cmd["answer"] = passwd + cmd["prompt_retry_check"] = True + + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), errors="surrogate_or_strict")) + prompt = self._get_prompt() + if prompt is None or not prompt.strip().endswith(b"enable#"): + raise AnsibleConnectionFailure( + "failed to elevate privilege to enable mode still at prompt [%s]" % prompt, + ) + except AnsibleConnectionFailure as e: + prompt = self._get_prompt() + raise AnsibleConnectionFailure( + "unable to elevate privilege to enable mode, at prompt [%s] with error: %s" + % (prompt, e.message), + ) + + 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 b"(config" in prompt: + self._exec_cli_command("end") + self._exec_cli_command("exit") + + elif prompt.endswith(b"enable#"): + self._exec_cli_command("exit") + + def on_open_shell(self): + try: + for cmd in ("terminal length 0", "terminal width 511"): + self._exec_cli_command(cmd) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure("unable to set terminal parameters") + + def validate_user_role(self): + user = self._connection._play_context.remote_user + + out = self._exec_cli_command("show user-account %s" % user) + out = to_text(out, errors="surrogate_then_replace").strip() + + match = re.search(r"roles:(.+)$", out, re.M) + if match: + roles = match.group(1).split() + if "network-admin" in roles: + return True + return False |