diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..9a7fc1251 --- /dev/null +++ b/.flake8 @@ -0,0 +1,67 @@ +[flake8] + +builtins = _ + +# Print the total number of errors: +count = true + +# Don't even try to analyze these: +extend-exclude = + # No need to traverse egg info dir + *.egg-info, + # tool cache dirs + *_cache + # project env vars + .env, + # GitHub configs + .github, + # Cache files of MyPy + .mypy_cache, + # Cache files of pytest + .pytest_cache, + # Temp dir of pytest-testmon + .tmontmp, + # Occasional virtualenv dir + .venv + # VS Code + .vscode, + # Temporary build dir + build, + # This contains sdists and wheels of ansible-navigator that we don't want to check + dist, + # Metadata of `pip wheel` cmd is autogenerated + pip-wheel-metadata, + # adjacent venv + venv + # ansible won't let me + __init__.py + +# IMPORTANT: avoid using ignore option, always use extend-ignore instead +# Completely and unconditionally ignore the following errors: +extend-ignore = + F841, + # line-length + E501, + # module level import not at top of file + E402 + +# Accessibility/large fonts and PEP8 unfriendly: +max-line-length = 120 + +# Allow certain violations in certain files: +# Please keep both sections of this list sorted, as it will be easier for others to find and add entries in the future +per-file-ignores = + # The following ignores have been researched and should be considered permanent + # each should be preceeded with an explanation of each of the error codes + # If other ignores are added for a specific file in the section following this, + # these will need to be added to that line as well. + + + # S101: Allow the use of assert within the tests directory, since tests require it. + tests/**.py: S101 + + # The following were present during the initial implementation. + # They are expected to be fixed and unignored over time. + +# Count the number of occurrences of each error/warning code and print a report: +statistics = true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5c01a71c7..64409a9a4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,8 @@ jobs: changelog: uses: ansible/ansible-content-actions/.github/workflows/changelog.yaml@main if: github.event_name == 'pull_request' + build-import: + uses: ansible/ansible-content-actions/.github/workflows/build_import.yaml@main ansible-lint: uses: ansible/ansible-content-actions/.github/workflows/ansible_lint.yaml@main sanity: @@ -32,6 +34,7 @@ jobs: if: ${{ always() }} needs: - changelog + - build-import - sanity - unit-galaxy - ansible-lint diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d034d8da3..8864467c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,18 @@ --- +ci: + # format compatible with commitlint + autoupdate_commit_msg: "chore: pre-commit autoupdate" + autoupdate_schedule: monthly + autofix_commit_msg: "chore: auto fixes from pre-commit.com hooks" + repos: - repo: https://github.com/ansible-network/collection_prep rev: 1.1.1 hooks: - # - id: autoversion # removed as being handled by GHA push and release drafter - id: update-docs - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-merge-conflict - id: check-symlinks @@ -23,7 +28,7 @@ repos: - id: add-trailing-comma - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.3" + rev: "v4.0.0-alpha.8" hooks: - id: prettier entry: env CI=1 bash -c "prettier --list-different . || ec=$? && prettier --loglevel=error --write . && exit $ec" @@ -34,13 +39,18 @@ repos: - prettier-plugin-toml - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort name: Sort import statements using isort args: ["--filter-files"] - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 24.4.2 hooks: - id: black + + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 diff --git a/.zuul.yaml b/.zuul.yaml deleted file mode 100644 index a892ec722..000000000 --- a/.zuul.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- project: - check: - jobs: - - ansible-tox-linters - gate: - jobs: - - ansible-tox-linters diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 70c2fc3b4..45cb5b069 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,38 @@ Cisco Iosxr Collection Release Notes .. contents:: Topics +v10.0.0 +======= + +Release Summary +--------------- + +Starting from this release, the minimum `ansible-core` version this collection requires is `2.15.0`. The last known version compatible with ansible-core<2.14 is `v9.0.0`. A new resource module `iosxr_vrf_global` is added to manage VRF global configurations. + +Major Changes +------------- + +- Bumping `requires_ansible` to `>=2.15.0`, since previous ansible-core versions are EoL now. + +Minor Changes +------------- + +- Adds a new module `iosxr_vrf_global` to manage VRF global configurations on Cisco IOS-XR devices (https://github.com/ansible-collections/cisco.iosxr/pull/467). + +v9.0.0 +====== + +Major Changes +------------- + +- Update the netcommon base version to support cli_restore plugin. + +Minor Changes +------------- + +- Add support for cli_restore functionality. +- Please refer the PR to know more about core changes (https://github.com/ansible-collections/ansible.netcommon/pull/618). +- cli_restore module is part of netcommon. v8.0.0 ====== @@ -370,7 +402,7 @@ Minor Changes New Modules ----------- -- iosxr_hostname - Manages hostname resource module +- iosxr_hostname - Resource module to configure hostname. v2.6.0 ====== @@ -395,7 +427,7 @@ Documentation Changes New Modules ----------- -- iosxr_snmp_server - Manages snmp-server resource module +- iosxr_snmp_server - Resource module to configure snmp server. v2.5.0 ====== @@ -431,7 +463,7 @@ Bugfixes New Modules ----------- -- iosxr_logging_global - Manages logging attributes of Cisco IOSXR network devices +- iosxr_logging_global - Resource module to configure logging. v2.3.0 ====== @@ -453,7 +485,7 @@ Bugfixes New Modules ----------- -- iosxr_prefix_lists - Prefix-Lists resource module. +- iosxr_prefix_lists - Resource module to configure prefix lists. v2.2.0 ====== @@ -539,9 +571,9 @@ Bugfixes New Modules ----------- -- iosxr_bgp_address_family - Manages BGP Address Family resource module. -- iosxr_bgp_global - Manages BGP global resource module. -- iosxr_bgp_neighbor_address_family - Manages BGP neighbor address family resource module. +- iosxr_bgp_address_family - Resource module to configure BGP Address family. +- iosxr_bgp_global - Resource module to configure BGP. +- iosxr_bgp_neighbor_address_family - Resource module to configure BGP Neighbor Address family. v1.2.1 ====== @@ -569,7 +601,7 @@ Bugfixes New Modules ----------- -- iosxr_ospf_interfaces - OSPF Interfaces Resource Module. +- iosxr_ospf_interfaces - Resource module to configure OSPF interfaces. v1.1.0 ====== @@ -583,7 +615,7 @@ Minor Changes New Modules ----------- -- iosxr_ospfv3 - ospfv3 resource module +- iosxr_ospfv3 - Resource module to configure OSPFv3. v1.0.5 ====== @@ -647,25 +679,22 @@ Netconf New Modules ----------- -- iosxr_acl_interfaces - ACL interfaces resource module -- iosxr_acls - ACLs resource module -- iosxr_banner - Manage multiline banners on Cisco IOS XR devices -- iosxr_bgp - Configure global BGP protocol settings on Cisco IOS-XR -- iosxr_command - Run commands on remote devices running Cisco IOS XR -- iosxr_config - Manage Cisco IOS XR configuration sections -- iosxr_facts - Get facts about iosxr devices. -- iosxr_interface - (deprecated, removed after 2022-06-01) Manage Interface on Cisco IOS XR network devices -- iosxr_interfaces - Interfaces resource module -- iosxr_l2_interfaces - L2 interfaces resource module -- iosxr_l3_interfaces - L3 interfaces resource module -- iosxr_lacp - LACP resource module -- iosxr_lacp_interfaces - LACP interfaces resource module -- iosxr_lag_interfaces - LAG interfaces resource module -- iosxr_lldp_global - LLDP resource module -- iosxr_lldp_interfaces - LLDP interfaces resource module -- iosxr_logging - Configuration management of system logging services on network devices +- iosxr_acl_interfaces - Resource module to configure ACL interfaces. +- iosxr_acls - Resource module to configure ACLs. +- iosxr_banner - Module to configure multiline banners. +- iosxr_command - Module to run commands on remote devices. +- iosxr_config - Module to manage configuration sections. +- iosxr_facts - Module to collect facts from remote devices. +- iosxr_interfaces - Resource module to configure interfaces. +- iosxr_l2_interfaces - Resource Module to configure L2 interfaces. +- iosxr_l3_interfaces - Resource module to configure L3 interfaces. +- iosxr_lacp - Resource module to configure LACP. +- iosxr_lacp_interfaces - Resource module to configure LACP interfaces. +- iosxr_lag_interfaces - Resource module to configure LAG interfaces. +- iosxr_lldp_global - Resource module to configure LLDP. +- iosxr_lldp_interfaces - Resource module to configure LLDP interfaces. - iosxr_netconf - Configures NetConf sub-system service on Cisco IOS-XR devices -- iosxr_ospfv2 - OSPFv2 resource module -- iosxr_static_routes - Static routes resource module -- iosxr_system - Manage the system attributes on Cisco IOS XR devices -- iosxr_user - Manage the aggregate of local users on Cisco IOS XR device +- iosxr_ospfv2 - Resource module to configure OSPFv2. +- iosxr_static_routes - Resource module to configure static routes. +- iosxr_system - Module to manage the system attributes. +- iosxr_user - Module to manage the aggregates of local users. diff --git a/README.md b/README.md index c0eb60006..0562113e5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This collection has been tested against Cisco IOS-XR version 7.0.2 ## Ansible version compatibility -This collection has been tested against following Ansible versions: **>=2.14.0**. +This collection has been tested against following Ansible versions: **>=2.15.0**. For collections that support Ansible 2.9, please ensure you update your `network_os` to use the fully qualified collection name (for example, `cisco.ios.ios`). @@ -69,8 +69,7 @@ Name | Description [cisco.iosxr.iosxr_static_routes](https://github.com/ansible-collections/cisco.iosxr/blob/main/docs/cisco.iosxr.iosxr_static_routes_module.rst)|Resource module to configure static routes. [cisco.iosxr.iosxr_system](https://github.com/ansible-collections/cisco.iosxr/blob/main/docs/cisco.iosxr.iosxr_system_module.rst)|Module to manage the system attributes. [cisco.iosxr.iosxr_user](https://github.com/ansible-collections/cisco.iosxr/blob/main/docs/cisco.iosxr.iosxr_user_module.rst)|Module to manage the aggregates of local users. -[cisco.iosxr.iosxr_vrf_address_family](https://github.com/ansible-collections/cisco.iosxr/blob/main/docs/cisco.iosxr.iosxr_vrf_address_family_module.rst)|Resource module to configure VRF Address family. -[cisco.iosxr.iosxr_vrfs](https://github.com/ansible-collections/cisco.iosxr/blob/main/docs/cisco.iosxr.iosxr_vrfs_module.rst)|Manages global VRF configuration. +[cisco.iosxr.iosxr_vrf_global](https://github.com/ansible-collections/cisco.iosxr/blob/main/docs/cisco.iosxr.iosxr_vrf_global_module.rst)|Manages global VRF configuration. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 2b66f7eca..ddfeceb9a 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -2,69 +2,61 @@ ancestor: null releases: 1.0.0: modules: - - description: ACL interfaces resource module + - description: Resource module to configure ACL interfaces. name: iosxr_acl_interfaces namespace: "" - - description: ACLs resource module + - description: Resource module to configure ACLs. name: iosxr_acls namespace: "" - - description: Manage multiline banners on Cisco IOS XR devices + - description: Module to configure multiline banners. name: iosxr_banner namespace: "" - - description: Configure global BGP protocol settings on Cisco IOS-XR - name: iosxr_bgp - namespace: "" - - description: Run commands on remote devices running Cisco IOS XR + - description: Module to run commands on remote devices. name: iosxr_command namespace: "" - - description: Manage Cisco IOS XR configuration sections + - description: Module to manage configuration sections. name: iosxr_config namespace: "" - - description: Get facts about iosxr devices. + - description: Module to collect facts from remote devices. name: iosxr_facts namespace: "" - - description: - (deprecated, removed after 2022-06-01) Manage Interface on Cisco - IOS XR network devices - name: iosxr_interface - namespace: "" - - description: Interfaces resource module + - description: Resource module to configure interfaces. name: iosxr_interfaces namespace: "" - - description: L2 interfaces resource module + - description: Resource Module to configure L2 interfaces. name: iosxr_l2_interfaces namespace: "" - - description: L3 interfaces resource module + - description: Resource module to configure L3 interfaces. name: iosxr_l3_interfaces namespace: "" - - description: LACP resource module + - description: Resource module to configure LACP. name: iosxr_lacp namespace: "" - - description: LACP interfaces resource module + - description: Resource module to configure LACP interfaces. name: iosxr_lacp_interfaces namespace: "" - - description: LAG interfaces resource module + - description: Resource module to configure LAG interfaces. name: iosxr_lag_interfaces namespace: "" - - description: LLDP resource module + - description: Resource module to configure LLDP. name: iosxr_lldp_global namespace: "" - - description: LLDP interfaces resource module + - description: Resource module to configure LLDP interfaces. name: iosxr_lldp_interfaces namespace: "" - description: Configures NetConf sub-system service on Cisco IOS-XR devices name: iosxr_netconf namespace: "" - - description: OSPFv2 resource module + - description: Resource module to configure OSPFv2. name: iosxr_ospfv2 namespace: "" - - description: Static routes resource module + - description: Resource module to configure static routes. name: iosxr_static_routes namespace: "" - - description: Manage the system attributes on Cisco IOS XR devices + - description: Module to manage the system attributes. name: iosxr_system namespace: "" - - description: Manage the aggregate of local users on Cisco IOS XR device + - description: Module to manage the aggregates of local users. name: iosxr_user namespace: "" plugins: @@ -130,7 +122,7 @@ releases: - 54_iosxr_ospfv3_module_added.yaml - provide_fuctionality_to_utilize_remarks.yaml modules: - - description: ospfv3 resource module + - description: Resource module to configure OSPFv3. name: iosxr_ospfv3 namespace: "" release_date: "2020-10-01" @@ -149,7 +141,7 @@ releases: - galaxy-version.yaml - ospf_interfaces_resource_module_added.yaml modules: - - description: OSPF Interfaces Resource Module. + - description: Resource module to configure OSPF interfaces. name: iosxr_ospf_interfaces namespace: "" release_date: "2020-11-26" @@ -196,13 +188,13 @@ releases: - single_user_mode.yaml - unittest-fix-for-resource-module-base.yaml modules: - - description: Manages BGP Address Family resource module. + - description: Resource module to configure BGP Address family. name: iosxr_bgp_address_family namespace: "" - - description: Manages BGP global resource module. + - description: Resource module to configure BGP. name: iosxr_bgp_global namespace: "" - - description: Manages BGP neighbor address family resource module. + - description: Resource module to configure BGP Neighbor Address family. name: iosxr_bgp_neighbor_address_family namespace: "" release_date: "2021-02-24" @@ -283,7 +275,7 @@ releases: - update_bgp_nbr_af_route_policy.yaml - update_readme_freenode_to_libera.yml modules: - - description: Prefix-Lists resource module. + - description: Resource module to configure prefix lists. name: iosxr_prefix_lists namespace: "" release_date: "2021-06-22" @@ -301,7 +293,7 @@ releases: - 162-fix-prefix-list-facts.yaml - add-iosxr-logging-global-module.yaml modules: - - description: Manages logging attributes of Cisco IOSXR network devices + - description: Resource module to configure logging. name: iosxr_logging_global namespace: "" release_date: "2021-07-26" @@ -344,7 +336,7 @@ releases: - fix_sanity.yml - iosxr_snmp_server.yaml modules: - - description: Manages snmp-server resource module + - description: Resource module to configure snmp server. name: iosxr_snmp_server namespace: "" release_date: "2021-12-07" @@ -356,7 +348,7 @@ releases: - add_hostname_rm.yaml - add_redirects_hostname.yaml modules: - - description: Manages hostname resource module + - description: Resource module to configure hostname. name: iosxr_hostname namespace: "" release_date: "2022-01-31" @@ -727,3 +719,33 @@ releases: - remove_deprecated.yaml - trivial_tests_updates.yaml release_date: "2024-03-27" + 9.0.0: + changes: + major_changes: + - Update the netcommon base version to support cli_restore plugin. + minor_changes: + - Add support for cli_restore functionality. + - Please refer the PR to know more about core changes (https://github.com/ansible-collections/ansible.netcommon/pull/618). + - cli_restore module is part of netcommon. + fragments: + - add_2.18.yaml + - add_cli_restore_supprt.yaml + - remove_tests.yaml + release_date: "2024-04-12" + 10.0.0: + changes: + major_changes: + - Bumping `requires_ansible` to `>=2.15.0`, since previous ansible-core versions + are EoL now. + minor_changes: + - Adds a new module `iosxr_vrf_global` to manage VRF global configurations on + Cisco IOS-XR devices (https://github.com/ansible-collections/cisco.iosxr/pull/467). + release_summary: + Starting from this release, the minimum `ansible-core` version + this collection requires is `2.15.0`. The last known version compatible with + ansible-core<2.15 is `v9.0.0`. A new resource module `iosxr_vrf_global` is + added to manage VRF global configurations. + fragments: + - add_vrf_global_module.yaml + - bump_215.yaml + release_date: "2024-06-06" diff --git a/galaxy.yml b/galaxy.yml index acaab18e6..d7c63ac5f 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ authors: - Ansible Network Community (ansible-network) dependencies: - "ansible.netcommon": ">=6.0.0" + "ansible.netcommon": ">=6.1.0" license_file: LICENSE name: iosxr namespace: cisco @@ -13,4 +13,4 @@ issues: https://github.com/ansible-collections/cisco.iosxr/issues tags: [cisco, iosxr, networking, netconf] # NOTE(pabelanger): We create an empty version key to keep ansible-galaxy # happy. We dynamically inject version info based on git information. -version: "8.0.0" +version: "10.0.0" diff --git a/meta/runtime.yml b/meta/runtime.yml index 5bf8ab65f..21ee0a2c5 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -1,5 +1,5 @@ --- -requires_ansible: ">=2.14.0" +requires_ansible: ">=2.15.0" plugin_routing: modules: acl_interfaces: @@ -60,3 +60,5 @@ plugin_routing: redirect: cisco.iosxr.iosxr_snmp_server hostname: redirect: cisco.iosxr.iosxr_hostname + vrf_global: + redirect: cisco.iosxr.iosxr_vrf_global diff --git a/plugins/action/vrf_global.py b/plugins/action/vrf_global.py new file mode 120000 index 000000000..4f317eb5d --- /dev/null +++ b/plugins/action/vrf_global.py @@ -0,0 +1 @@ +iosxr.py \ No newline at end of file diff --git a/plugins/cliconf/iosxr.py b/plugins/cliconf/iosxr.py index 6b569ae2a..48d08fc8c 100644 --- a/plugins/cliconf/iosxr.py +++ b/plugins/cliconf/iosxr.py @@ -358,6 +358,15 @@ def edit_config( resp["response"] = results return resp + def restore(self, filename=None, path=""): + if not filename: + raise ValueError("'file_name' value is required for restore") + self.configure() + cmd = f"load {path}{filename}" + resp = self.send_command(cmd) + self.commit() + return resp + def get_diff( self, candidate=None, @@ -452,21 +461,29 @@ def commit(self, comment=None, label=None, replace=None): if self.get_option("commit_confirmed"): cmd_obj["command"] = "commit replace confirmed" if self.get_option("commit_confirmed_timeout"): - cmd_obj["command"] += " {0}".format(self.get_option("commit_confirmed_timeout")) + cmd_obj["command"] += " {0}".format( + self.get_option("commit_confirmed_timeout"), + ) - cmd_obj[ - "prompt" - ] = "This commit will replace or remove the entire running configuration" + cmd_obj["prompt"] = ( + "This commit will replace or remove the entire running configuration" + ) cmd_obj["answer"] = "yes" elif self.get_option("commit_confirmed"): cmd_obj["command"] = "commit confirmed" if self.get_option("commit_confirmed_timeout"): - cmd_obj["command"] += " {0}".format(self.get_option("commit_confirmed_timeout")) + cmd_obj["command"] += " {0}".format( + self.get_option("commit_confirmed_timeout"), + ) if self.get_option("commit_label"): - cmd_obj["command"] += " label {0}".format(self.get_option("commit_label")) + cmd_obj["command"] += " label {0}".format( + self.get_option("commit_label"), + ) if self.get_option("commit_comment"): - cmd_obj["command"] += " comment {0}".format(self.get_option("commit_comment")) + cmd_obj["command"] += " comment {0}".format( + self.get_option("commit_comment"), + ) else: label = label or self.get_option("commit_label") @@ -498,7 +515,9 @@ def run_commands(self, commands=None, check_rc=True): output = cmd.pop("output", None) if output: - raise ValueError("'output' value %s is not supported for run_commands" % output) + raise ValueError( + "'output' value %s is not supported for run_commands" % output, + ) try: out = self.send_command(**cmd) diff --git a/plugins/module_utils/network/iosxr/argspec/vrf_global/__init__.py b/plugins/module_utils/network/iosxr/argspec/vrf_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/iosxr/argspec/vrf_global/vrf_global.py b/plugins/module_utils/network/iosxr/argspec/vrf_global/vrf_global.py new file mode 100644 index 000000000..cf2ca4621 --- /dev/null +++ b/plugins/module_utils/network/iosxr/argspec/vrf_global/vrf_global.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 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 +# ansible.content_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the documentation in the module file and re-run +# ansible.content_builder commenting out +# the path to external 'docstring' in build.yaml. +# +############################################## + +""" +The arg spec for the iosxr_vrf_global module +""" + + +class Vrf_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the iosxr_vrf_global module""" + + argument_spec = { + "config": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str", "required": True}, + "description": {"type": "str"}, + "evpn_route_sync": {"type": "int"}, + "fallback_vrf": {"type": "str"}, + "mhost": { + "type": "dict", + "options": { + "afi": {"type": "str", "choices": ["ipv4", "ipv6"]}, + "default_interface": {"type": "str"}, + }, + }, + "rd": {"type": "str"}, + "remote_route_filtering": { + "type": "dict", + "options": {"disable": {"type": "bool"}}, + }, + "vpn": {"type": "dict", "options": {"id": {"type": "str"}}}, + }, + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "parsed", + "gathered", + "deleted", + "merged", + "replaced", + "rendered", + "overridden", + "purged", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/iosxr/config/vrf_global/__init__.py b/plugins/module_utils/network/iosxr/config/vrf_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/iosxr/config/vrf_global/vrf_global.py b/plugins/module_utils/network/iosxr/config/vrf_global/vrf_global.py new file mode 100644 index 000000000..7c5045976 --- /dev/null +++ b/plugins/module_utils/network/iosxr/config/vrf_global/vrf_global.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 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 iosxr_vrf_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.iosxr.plugins.module_utils.network.iosxr.facts.facts import Facts +from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.rm_templates.vrf_global import ( + Vrf_globalTemplate, +) + + +class Vrf_global(ResourceModule): + """ + The iosxr_vrf_global config class + """ + + def __init__(self, module): + super(Vrf_global, self).__init__( + empty_fact_val=[], + facts_module=Facts(module), + module=module, + resource="vrf_global", + tmplt=Vrf_globalTemplate(), + ) + self.parsers = [ + "description", + "evpn_route_sync", + "fallback_vrf", + "mhost.default_interface", + "rd", + "remote_route_filtering.disable", + "vpn.id", + ] + + 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.want + haved = self.have + + wantd = self._vrf_list_to_dict(wantd) + haved = self._vrf_list_to_dict(haved) + + # if state is merged, merge want into 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 haved.items() 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 haved.items(): + if k not in wantd: + self._compare(want={}, have=have, vrf=k) + + if self.state == "purged": + for k, have in iteritems(haved): + self.purge(have) + + for k, want in wantd.items(): + self._compare(want=want, have=haved.pop(k, {}), vrf=k) + + 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 Vrf network resource. + """ + begin = len(self.commands) + self.compare(self.parsers, want=want, have=have) + if len(self.commands) != begin: + self.commands.insert(begin, self._tmplt.render({"name": vrf}, "name", False)) + + def _vrf_list_to_dict(self, entry): + """Convert list of items to dict of items + for efficient diff calculation. + :params entry: data dictionary + """ + entry = {x["name"]: x for x in entry} + return entry + + def purge(self, have): + """Purge the VRF configuration""" + self.commands.append("no vrf {0}".format(have["name"])) diff --git a/plugins/module_utils/network/iosxr/facts/facts.py b/plugins/module_utils/network/iosxr/facts/facts.py index 37967eebe..9588520ff 100644 --- a/plugins/module_utils/network/iosxr/facts/facts.py +++ b/plugins/module_utils/network/iosxr/facts/facts.py @@ -97,6 +97,9 @@ from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.facts.vrf_address_family.vrf_address_family import ( Vrf_address_familyFacts, ) +from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.facts.vrf_global.vrf_global import ( + Vrf_globalFacts, +) FACT_LEGACY_SUBSETS = dict( @@ -130,6 +133,7 @@ hostname=HostnameFacts, bgp_templates=Bgp_templatesFacts, vrf_address_family=Vrf_address_familyFacts, + vrf_global=Vrf_globalFacts, ) diff --git a/plugins/module_utils/network/iosxr/facts/vrf_global/__init__.py b/plugins/module_utils/network/iosxr/facts/vrf_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/iosxr/facts/vrf_global/vrf_global.py b/plugins/module_utils/network/iosxr/facts/vrf_global/vrf_global.py new file mode 100644 index 000000000..d24167745 --- /dev/null +++ b/plugins/module_utils/network/iosxr/facts/vrf_global/vrf_global.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 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 iosxr vrf_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.iosxr.plugins.module_utils.network.iosxr.argspec.vrf_global.vrf_global import ( + Vrf_globalArgs, +) +from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.rm_templates.vrf_global import ( + Vrf_globalTemplate, +) +from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.utils.utils import ( + flatten_config, +) + + +class Vrf_globalFacts(object): + """The iosxr vrf facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Vrf_globalArgs.argument_spec + + def get_config(self, connection): + """Get the configuration from the device""" + + return connection.get("show running-config vrf") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Vrf network resource + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + + facts = {} + objs = [] + obj = {} + + if not data: + data = self.get_config(connection) + + data = flatten_config(data, "vrf") + + # parse native config using the Vrf_global template + vrf_global_parser = Vrf_globalTemplate(lines=data.splitlines(), module=self._module) + obj = vrf_global_parser.parse() + objs = list(obj.values()) + + ansible_facts["ansible_network_resources"].pop("vrf_global", None) + params = utils.remove_empties( + vrf_global_parser.validate_config( + self.argument_spec, + {"config": objs}, + redact=True, + ), + ) + + facts["vrf_global"] = params.get("config", []) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/plugins/module_utils/network/iosxr/rm_templates/vrf_global.py b/plugins/module_utils/network/iosxr/rm_templates/vrf_global.py new file mode 100644 index 000000000..a225e3685 --- /dev/null +++ b/plugins/module_utils/network/iosxr/rm_templates/vrf_global.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 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 Vrf_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 Vrf_globalTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(Vrf_globalTemplate, self).__init__( + lines=lines, + tmplt=self, + module=module, + ) + + # fmt: off + PARSERS = [ + { + "name": "name", + "getval": re.compile( + r""" + ^vrf\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "vrf {{ name }}", + "result": { + '{{ name }}': { + 'name': '{{ name }}', + }, + }, + "shared": True, + }, + { + "name": "description", + "getval": re.compile( + r""" + ^vrf\s(?P\S+) + \s+description\s(?P.+$) + $""", re.VERBOSE, + ), + "setval": "description {{ description }}", + "result": { + '{{ name }}': { + 'name': '{{ name }}', + 'description': '{{ description }}', + }, + }, + }, + { + "name": "evpn_route_sync", + "getval": re.compile( + r""" + ^vrf\s(?P\S+) + \s+evpn-route-sync\s(?P\d+) + $""", re.VERBOSE, + ), + "setval": "evpn-route-sync {{ evpn_route_sync }}", + "result": { + '{{ name }}': { + 'name': '{{ name }}', + "evpn_route_sync": "{{ evpn_route_sync }}", + }, + }, + }, + { + "name": "fallback_vrf", + "getval": re.compile( + r""" + ^vrf\s(?P\S+) + \s+fallback-vrf\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "fallback-vrf {{ fallback_vrf }}", + "result": { + '{{ name }}': { + 'name': '{{ name }}', + "fallback_vrf": "{{ fallback_vrf }}", + }, + }, + }, + { + "name": "mhost.default_interface", + "getval": re.compile( + r""" + ^vrf\s(?P\S+) + (?P\s+mhost\s(?P\S+)) + \s+default-interface\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "mhost {{ mhost.afi }} default-interface {{ mhost.default_interface }}", + "compval": "mhost.default_interface", + "result": { + '{{ name }}': { + 'name': '{{ name }}', + "mhost": { + "afi": "{{ afi }}", + "default_interface": "{{ default_interface }}", + }, + }, + }, + }, + { + "name": "rd", + "getval": re.compile( + r""" + ^vrf\s(?P\S+) + \s+rd\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "rd {{ rd }}", + "compval": "rd", + "result": { + '{{ name }}': { + 'name': '{{ name }}', + "rd": "{{ rd }}", + }, + }, + }, + { + "name": "remote_route_filtering.disable", + "getval": re.compile( + r""" + ^vrf\s(?P\S+) + \s+remote-route-filtering\s(?Pdisable) + $""", re.VERBOSE, + ), + "setval": "remote-route-filtering disable", + "compval": "remote_route_filtering.disable", + "result": { + '{{ name }}': { + 'name': '{{ name }}', + "remote_route_filtering": { + "disable": "{{ true if disable is defined }}", + }, + }, + }, + }, + { + "name": "vpn.id", + "getval": re.compile( + r""" + ^vrf\s(?P\S+) + \s+vpn\sid\s(?P\S+) + $""", re.VERBOSE, + ), + "setval": "vpn id {{ vpn.id }}", + "compval": "vpn.id", + "result": { + '{{ name }}': { + 'name': '{{ name }}', + "vpn": { + "id": "{{ vpn_id }}", + }, + }, + }, + }, + ] + # fmt: on diff --git a/plugins/modules/iosxr_vrf_global.py b/plugins/modules/iosxr_vrf_global.py new file mode 100644 index 000000000..4531faa72 --- /dev/null +++ b/plugins/modules/iosxr_vrf_global.py @@ -0,0 +1,740 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for iosxr_vrf_global +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: iosxr_vrf_global +short_description: Manages global VRF configuration. +description: + - This module manages VRF configurations on Cisco IOS-XR devices. + - It enables playbooks to handle either individual VRFs or the complete VRF collection. + - It also permits removing non-explicitly stated VRF definitions from the setup. +version_added: 9.0.0 +author: Ruchi Pakhle (@Ruchip16) +notes: + - Tested against Cisco IOS-XR Version 9.0.0 + - This module works with connection C(network_cli). + - See L(the IOS_XR Platform Options, https://github.com/ansible-collections/cisco.iosxr/blob/main/platform_guide.rst). + - The module examples uses callback plugin (stdout_callback = yaml) to generate task output in yaml format. +options: + config: + description: A dictionary of options for VRF configurations. + type: list + elements: dict + suboptions: + name: + description: Name of the VRF. + type: str + required: true + description: + description: A description for the VRF. + type: str + evpn_route_sync: + description: EVPN Instance VPN ID used to synchronize the VRF route(s). + type: int + fallback_vrf: + description: Fallback VRF name + type: str + mhost: + description: Multicast host stack options + type: dict + suboptions: + afi: + description: Address Family Identifier (AFI) + type: str + choices: ['ipv4', 'ipv6'] + default_interface: + description: Default interface for multicast. + type: str + rd: + description: VPN Route Distinguisher (RD). + type: str + remote_route_filtering: + description: Enable/Disable remote route filtering per VRF + type: dict + suboptions: + disable: + description: Disable remote route filtering per VRF + type: bool + vpn: + description: VPN ID for the VRF + type: dict + suboptions: + id: + description: VPN ID for the VRF. + type: str + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the IOS-XR device by + executing the command B(show running-config vrf). + - 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 + state: + choices: [parsed, gathered, deleted, merged, replaced, rendered, overridden, purged] + default: merged + description: + - The state the configuration should be left in + - The states I(rendered), I(gathered) and I(parsed) does not perform any change + on the device. + - The state I(rendered) will transform the configuration in C(config) option to + platform specific CLI commands which will be returned in the I(rendered) key + within the result. For state I(rendered) active connection to remote host is + not required. + - The state I(gathered) will fetch the running configuration from device and transform + it into structured data in the format as per the resource module argspec and + the value is returned in the I(gathered) key within the result. + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into JSON format as per the resource module parameters and the + value is returned in the I(parsed) key within the result. The value of C(running_config) + option should be the same format as the output of command I(show running-config vrf). + connection to remote host is not required. + - The state I(purged) removes all the VRF configurations from the + target device. Use caution with this state. + - The state I(deleted) only removes the VRF attributes that this module + manages and does not negate the VRF completely. Thereby, preserving + address-family related configurations under VRF context. + - Refer to examples for more details. + type: str +""" + +EXAMPLES = """ + +# Using merged +# +# Before state: +# ------------- +# +# RP/0/0/CPU0:iosxr-02#show running-config vrf +# Fri Feb 9 07:02:35.789 UTC +# ! +# vrf test +# + +- name: Merge provided configuration with device configuration + cisco.iosxr.iosxr_vrf_global: + config: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: "test-vrf" + remote_route_filtering: + disable: "true" + rd: "3:4" + mhost: + afi: "ipv4" + default_interface: "Loopback0" + vpn: + id: "2:3" + state: merged + +# Task Output: +# ------------ +# +# before: [] +# +# commands: +# - vrf VRF4 +# - description VRF4 Description +# - evpn-route-sync 793 +# - fallback-vrf test-vrf +# - mhost ipv4 default-interface Loopback0 +# - rd 3:4 +# - remote-route-filtering disable +# - vpn id 2:3 +# +# after: +# - name: VRF4 +# description: VRF4 Description +# evpn_route_sync: 793 +# fallback_vrf: "test-vrf" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# rd: "3:4" +# remote_route_filtering: +# disable: "true" +# vpn: +# id: "2:3" +# +# After state: +# ------------ +# +# RP/0/0/CPU0:iosxr-02#show running-config vrf +# Sat Feb 20 03:49:43.618 UTC +# vrf VRF4 +# description "VRF4 Description" +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 793 +# vpn id 2:3 +# fallback-vrf "test-vrf" +# remote-route-filtering disable +# rd "3:4" + +# Using replaced +# +# Before state: +# ------------- +# +# RP/0/0/CPU0:iosxr-02#show running-config vrf +# Sat Feb 20 03:49:43.618 UTC +# vrf VRF4 +# description "VRF4 Description" +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 793 +# vpn id 2:3 +# fallback-vrf "test-vrf" +# remote-route-filtering disable +# rd "3:4" + +- name: Replace the provided configuration with the existing running configuration + cisco.iosxr.iosxr_vrf_global: + config: + - name: VRF7 + description: VRF7 description + evpn_route_sync: 398 + fallback_vrf: "replaced-vrf" + remote_route_filtering: + disable: "true" + rd: "67:9" + mhost: + afi: "ipv4" + default_interface: "Loopback0" + vpn: + id: "4:5" + state: replaced + +# Task Output: +# ------------ +# +# before: +# - name: VRF4 +# description: VRF4 Description +# evpn_route_sync: 793 +# fallback_vrf: "test-vrf" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# rd: "3:4" +# remote_route_filtering: +# disable: "true" +# vpn: +# id: "2:3" +# +# commands: +# - vrf VRF4 +# - no vpn id 2:3 +# - vrf VRF7 +# - description VRF7 description +# - evpn-route-sync 398 +# - fallback-vrf replaced-vrf +# - mhost ipv4 default-interface Loopback0 +# - rd 6:9 +# - remote-route-filtering disable +# - vpn id 4:5 +# +# after: +# - name: VRF4 +# description: VRF4 Description +# evpn_route_sync: 793 +# fallback_vrf: "test-vrf" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# rd: "3:4" +# remote_route_filtering: +# disable: "true" +# - name: VRF7 +# description: VRF7 description +# evpn_route_sync: 398 +# fallback_vrf: "replaced-vrf" +# remote_route_filtering: +# disable: true +# rd: "67:9" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# vpn: +# id: "4:5" +# +# After state: +# ------------ +# +# RP/0/RP0/CPU0:ios(config)#show running-config vrf +# Sun Mar 10 16:48:53.204 UTC +# vrf VRF4 +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 793 +# description VRF4 Description +# fallback-vrf test-vrf +# remote-route-filtering disable +# rd 3:4 +# ! +# vrf VRF7 +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 398 +# description VRF7 description +# vpn id 4:5 +# fallback-vrf replaced-vrf +# remote-route-filtering disable +# rd 67:9 +# ! +# ! + +# Using overridden +# +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios(config)#show running-config vrf +# Sun Mar 10 16:48:53.204 UTC +# vrf VRF4 +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 793 +# description VRF4 Description +# fallback-vrf test-vrf +# remote-route-filtering disable +# rd 3:4 +# ! +# ! +# vrf VRF7 +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 398 +# description VRF7 description +# vpn id 4:5 +# fallback-vrf replaced-vrf +# remote-route-filtering disable +# rd 67:9 +# ! +# ! + +- name: Override the provided configuration with the existing running configuration + cisco.iosxr.iosxr_vrf_global: + state: overridden + config: + - name: VRF6 + description: VRF6 Description + evpn_route_sync: 101 + fallback_vrf: "overridden-vrf" + remote_route_filtering: + disable: "true" + rd: "9:8" + mhost: + afi: "ipv4" + default_interface: "Loopback0" + vpn: + id: "23:3" + +# Task Output: +# ------------ +# +# before: +# - name: VRF4 +# description: VRF4 Description +# evpn_route_sync: 793 +# fallback_vrf: "test-vrf" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# rd: "3:4" +# remote_route_filtering: +# disable: "true" +# - name: VRF7 +# description: VRF7 description +# evpn_route_sync: 398 +# fallback_vrf: "replaced-vrf" +# remote_route_filtering: +# disable: true +# rd: "67:9" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# vpn: +# id: "4:5" +# +# commands: +# - vrf VRF4 +# - no description VRF4 Description +# - no evpn-route-sync 793 +# - no fallback-vrf test-vrf +# - no mhost ipv4 default-interface Loopback0 +# - no rd 3:4 +# - no remote-route-filtering disable +# - vrf VRF7 +# - no description VRF7 description +# - no evpn-route-sync 398 +# - no fallback-vrf replaced-vrf +# - no mhost ipv4 default-interface Loopback0 +# - no rd 67:9 +# - no remote-route-filtering disable +# - no vpn id 4:5 +# - vrf VRF6 +# - description VRF6 Description +# - evpn-route-sync 101 +# - fallback-vrf overridden-vrf +# - mhost ipv4 default-interface Loopback0 +# - rd 9:8 +# - remote-route-filtering disable +# - vpn id 23:3 +# +# after: +# - name: VRF4 +# - name: VRF6 +# description: VRF6 Description +# evpn_route_sync: 101 +# fallback_vrf: "overridden-vrf" +# remote_route_filtering: +# disable: "true" +# rd: "9:8" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# vpn: +# id: "23:3" +# - name: VRF7 +# +# After state: +# ------------- +# RP/0/RP0/CPU0:ios(config)#show running-config vrf +# Sun Mar 10 16:54:53.007 UTC +# vrf VRF4 +# vrf VRF6 +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 101 +# description VRF6 Description +# vpn id 23:3 +# fallback-vrf overridden-vrf +# remote-route-filtering disable +# rd 9:8 +# vrf VRF7 + +# Using deleted +# +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios(config)#show running-config vrf +# Sun Mar 10 16:54:53.007 UTC +# vrf VRF4 +# vrf VRF6 +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 101 +# description VRF6 Description +# vpn id 23:3 +# fallback-vrf overridden-vrf +# remote-route-filtering disable +# rd 9:8 +# vrf VRF7 + +- name: Delete the provided configuration + cisco.iosxr.iosxr_vrf_global: + config: + state: deleted + +# Task Output: +# ------------ +# +# before: +# - name: VRF4 +# - name: VRF6 +# description: VRF6 Description +# evpn_route_sync: 101 +# fallback_vrf: "overridden-vrf" +# remote_route_filtering: +# disable: "true" +# rd: "9:8" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# vpn: +# id: "23:3" +# - name: VRF7 + +# commands: +# - vrf VRF4 +# - vrf VRF6 +# - no description VRF6 Description +# - no evpn-route-sync 101 +# - no fallback-vrf overridden-vrf +# - no mhost ipv4 default-interface Loopback0 +# - no rd 9:8 +# - no remote-route-filtering disable +# - no vpn id 23:3 +# - vrf VRF7 +# +# after: +# - name: VRF4 +# - name: VRF6 +# - name: VRF7 +# +# After state: +# ------------ +# +# RP/0/RP0/CPU0:ios(config)#show running-config vrf +# Sun Mar 10 17:02:38.981 UTC +# vrf VRF4 +# vrf VRF6 +# vrf VRF7 + +# Using purged +# +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios(config)#show running-config vrf +# vrf VRF4 +# vrf VRF6 +# vrf VRF7 + +- name: Purge all the configuration from the device + cisco.iosxr.iosxr_vrf_global: + state: purged + +# Task Output: +# ------------ +# +# before: +# - name: VRF4 +# - name: VRF6 +# - name: VRF7 +# +# commands: +# - no vrf VRF4 +# - no vrf VRF6 +# - no vrf VRF7 +# +# after: [] +# +# After state: +# ------------- +# RP/0/RP0/CPU0:ios(config)#show running-config vrf +# Sun Mar 10 17:02:38.981 UTC +# - + +# Using rendered +# +- name: Render provided configuration with device configuration + cisco.iosxr.iosxr_vrf_global: + config: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: "test-vrf" + remote_route_filtering: + disable: "true" + rd: "3:4" + mhost: + afi: "ipv4" + default_interface: "Loopback0" + vpn: + id: "2:3" + state: rendered + +# Task Output: +# ------------ +# +# rendered: +# - vrf VRF4 +# - description VRF4 Description +# - evpn-route-sync 793 +# - fallback-vrf test-vrf +# - mhost ipv4 default-interface Loopback0 +# - rd 3:4 +# - remote-route-filtering disable +# - vpn id 2:3 + +# Using gathered +# +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios(config)#show running-config vrf +# Sun Mar 10 17:02:38.981 UTC +# vrf VRF4 +# description "VRF4 Description" +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 793 +# vpn id 2:3 +# fallback-vrf "test-vrf" +# remote-route-filtering disable +# rd "3:4" + +- name: Gather existing running configuration + cisco.iosxr.iosxr_vrf_global: + state: gathered + +# Task Output: +# ------------ +# +# gathered: +# - name: VRF4 +# description: VRF4 Description +# evpn_route_sync: 793 +# fallback_vrf: "test-vrf" +# mhost: +# afi: "ipv4" +# default_interface: "Loopback0" +# rd: "3:4" +# remote_route_filtering: +# disable: "true" +# vpn: +# id: "2:3" + +# Using parsed +# +# File: parsed.cfg +# ---------------- +# +# vrf test +# description "This is test VRF" +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 456 +# vpn id 56 +# fallback-vrf "test-vrf" +# remote-route-filtering disable +# rd "testing" +# ! +# ! +# vrf my_vrf +# mhost ipv4 default-interface Loopback0 +# evpn-route-sync 235 +# description "this is sample vrf for feature testing" +# fallback-vrf "parsed-vrf" +# rd "2:3" +# remote-route-filtering disable +# vpn id 23 +# ! +# ! + +- name: Parse the provided configuration + cisco.iosxr.iosxr_vrf_global: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Task Output: +# ------------ +# +# parsed: +# - description: This is test VRF +# evpn_route_sync: 456 +# fallback_vrf: test-vrf +# mhost: +# afi: ipv4 +# default_interface: Loopback0 +# name: test +# rd: testing +# remote_route_filtering: +# disable: true +# vpn: +# id: '56' +# - description: this is sample vrf for feature testing +# evpn_route_sync: 235 +# fallback_vrf: parsed-vrf +# mhost: +# afi: ipv4 +# default_interface: Loopback0 +# name: my_vrf +# rd: '2:3' +# remote_route_filtering: +# disable: true +# vpn: +# id: '23' +""" + +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: + - vrf VRF7 + - description VRF7 description + - rd: 67:9 + - fallback-vrf replaced-vrf +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - vrf VRF4 + - description VRF4 Description + - evpn-route-sync 793 + - fallback-vrf parsed-vrf +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.iosxr.plugins.module_utils.network.iosxr.argspec.vrf_global.vrf_global import ( + Vrf_globalArgs, +) +from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.config.vrf_global.vrf_global import ( + Vrf_global, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=Vrf_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 = Vrf_global(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/test-requirements.txt b/test-requirements.txt index b8dae64f8..685cbbe94 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,9 +1,3 @@ -black==23.3.0 -flake8 -mock -pexpect -yamllint -coverage==4.5.4 google-api-python-client grpcio protobuf diff --git a/tests/integration/targets/iosxr_smoke/tests/netconf/common_netconf.yaml b/tests/integration/targets/iosxr_smoke/tests/netconf/common_netconf.yaml index 2ab0957ae..5f08fd677 100644 --- a/tests/integration/targets/iosxr_smoke/tests/netconf/common_netconf.yaml +++ b/tests/integration/targets/iosxr_smoke/tests/netconf/common_netconf.yaml @@ -23,24 +23,4 @@ - "'this is my login banner' in result.xml" - "'that has a multiline' in result.xml" -# hit etree_findall() -- name: Remove host logging - cisco.iosxr.iosxr_logging: - dest: host - name: 172.16.0.1 - state: absent - -- name: Set up syslog host logging - cisco.iosxr.iosxr_logging: - dest: host - name: 172.16.0.1 - level: errors - state: present - register: result - -- ansible.builtin.assert: - that: - - "result.changed == true" - - '"172.16.0.1" in result.xml[0]' - - ansible.builtin.debug: msg="END iosxr netconf/common_netconf.yaml on connection={{ ansible_connection }}" diff --git a/tests/integration/targets/iosxr_vrf_global/defaults/main.yaml b/tests/integration/targets/iosxr_vrf_global/defaults/main.yaml new file mode 100644 index 000000000..871ea460c --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "[^_].*" diff --git a/tests/integration/targets/iosxr_vrf_global/meta/main.yaml b/tests/integration/targets/iosxr_vrf_global/meta/main.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/targets/iosxr_vrf_global/tasks/cli.yaml b/tests/integration/targets/iosxr_vrf_global/tasks/cli.yaml new file mode 100644 index 000000000..86ca0d9cb --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tasks/cli.yaml @@ -0,0 +1,22 @@ +--- +- name: Collect all CLI test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/common" + patterns: "{{ testcase }}.yaml" + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + ansible.builtin.set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=ansible.netcommon.network_cli) + ansible.builtin.include_tasks: "{{ test_case_to_run }}" + vars: + ansible_connection: ansible.netcommon.network_cli + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run + tags: + - network_cli diff --git a/tests/integration/targets/iosxr_vrf_global/tasks/main.yaml b/tests/integration/targets/iosxr_vrf_global/tasks/main.yaml new file mode 100644 index 000000000..f75f2f031 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tasks/main.yaml @@ -0,0 +1,5 @@ +--- +- name: Include the CLI tasks + ansible.builtin.include_tasks: cli.yaml + tags: + - cli diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/_parsed.cfg b/tests/integration/targets/iosxr_vrf_global/tests/common/_parsed.cfg new file mode 100644 index 000000000..e861b123d --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/_parsed.cfg @@ -0,0 +1,8 @@ +vrf test + description "This is test VRF" + mhost ipv4 default-interface Loopback0 + evpn-route-sync 456 + vpn id "56" + fallback-vrf "test-vrf" + remote-route-filtering disable + rd "testing" diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/_populate.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/_populate.yaml new file mode 100644 index 000000000..a75f16da2 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/_populate.yaml @@ -0,0 +1,13 @@ +--- +- name: Merge the provided configuration with the existing running configuration + register: result + cisco.iosxr.iosxr_config: + lines: + - vrf VRF4 + - description VRF4 Description + - evpn-route-sync 793 + - fallback-vrf merged-vrf + - mhost ipv4 default-interface Loopback0 + - rd 3:4 + - remote-route-filtering disable + - vpn id 2:3 diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/_remove_config.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/_remove_config.yaml new file mode 100644 index 000000000..d1bf0ab74 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/_remove_config.yaml @@ -0,0 +1,9 @@ +--- +- name: Remove VRF global configurations + register: result + cisco.iosxr.iosxr_config: + lines: + - no vrf VRF4 + - no vrf VRF6 + - no vrf VRF7 + - no vrf test diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/deleted.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/deleted.yaml new file mode 100644 index 000000000..5dd4a02fd --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/deleted.yaml @@ -0,0 +1,43 @@ +--- +- ansible.builtin.debug: + msg: Start iosxr_vrf_global deleted integration tests connection={{ ansible_connection}} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- ansible.builtin.include_tasks: _populate.yaml + +- block: + - name: Delete given vrf configuration + register: result + cisco.iosxr.iosxr_vrf_global: &deleted + config: + state: deleted + + - ansible.builtin.assert: + that: + - result.changed == true + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - "{{ deleted['before'] | symmetric_difference(result['before']) | length == 0 }}" + + - name: Assert that after dicts are correctly generated + ansible.builtin.assert: + that: + - deleted['after'] == result['after'] + + - name: Idempotency check + register: result + cisco.iosxr.iosxr_vrf_global: *deleted + - ansible.builtin.assert: + that: + - result.commands|length == 0 + - result.changed == false + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/empty_config.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/empty_config.yaml new file mode 100644 index 000000000..fc97c9688 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/empty_config.yaml @@ -0,0 +1,68 @@ +--- +- ansible.builtin.debug: + msg: START iosxr_vrf_global empty_config integration tests on connection={{ ansible_connection }} + +- name: Merged with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.iosxr.iosxr_vrf_global: + config: + state: merged + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.iosxr.iosxr_vrf_global: + config: + state: replaced + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.iosxr.iosxr_vrf_global: + config: + state: overridden + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Rendered with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.iosxr.iosxr_vrf_global: + config: + state: rendered + +- ansible.builtin.assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' + +- name: Parsed with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.iosxr.iosxr_vrf_global: + running_config: + state: parsed + +- ansible.builtin.assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state parsed' + +- name: Purged with empty configuration should give appropriate error message + register: result + ignore_errors: true + cisco.iosxr.iosxr_vrf_global: + config: + state: purged + +- ansible.builtin.debug: + msg: END iosxr_vrf_global empty_config integration tests on connection={{ ansible_connection }} diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/gathered.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/gathered.yaml new file mode 100644 index 000000000..b1bf01f59 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/gathered.yaml @@ -0,0 +1,22 @@ +--- +- ansible.builtin.debug: + msg: START iosxr_vrf_global gathered integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- ansible.builtin.include_tasks: _populate.yaml + +- block: + - name: Gather existing running configuration + register: result + cisco.iosxr.iosxr_vrf_global: + state: gathered + + - name: Assert + ansible.builtin.assert: + that: + - result.changed == false + - gathered['after'] == result['gathered'] + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/merged.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/merged.yaml new file mode 100644 index 000000000..83d560d20 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/merged.yaml @@ -0,0 +1,50 @@ +--- +- ansible.builtin.debug: + msg: Start iosxr_vrf_global merged integration tests connection={{ ansible_connection}} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- block: + - name: Merge provided configuration with device configuration + cisco.iosxr.iosxr_vrf_global: &merged + config: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: merged-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "3:4" + remote_route_filtering: + disable: true + vpn: + id: "2:3" + state: merged + register: result + + - name: Assert that before dicts were correctly generated + ansible.builtin.assert: + that: "{{ result['before'] == [] }}" + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that after dicts were correctly generated + ansible.builtin.assert: + that: + - "{{ merged['after'] == result['after'] }}" + + - name: Merge the provided configuration with the existing running configuration (idempotent) + cisco.iosxr.iosxr_vrf_global: *merged + register: result + + - name: Assert that the previous task was idempotent + ansible.builtin.assert: + that: + - result['changed'] == false + - result.commands|length == 0 + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/overridden.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/overridden.yaml new file mode 100644 index 000000000..602a953b1 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/overridden.yaml @@ -0,0 +1,58 @@ +--- +- ansible.builtin.debug: + msg: START iosxr_vrf_global overridden integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _populate.yaml + +- block: + - name: Override the provided configuration with the existing running configuration + cisco.iosxr.iosxr_vrf_global: &overridden + config: + - name: VRF4 + - name: VRF6 + description: VRF6 Description + evpn_route_sync: 101 + fallback_vrf: overridden-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "9:8" + remote_route_filtering: + disable: true + vpn: + id: "23:3" + state: overridden + register: result + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - "{{ overridden['before'] == result['before'] }}" + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Idempotency check + cisco.iosxr.iosxr_vrf_global: *overridden + register: result + + - name: Assert that no changes were made + ansible.builtin.assert: + that: + - result['changed'] == false + - result.commands|length == 0 + + - name: Assert that before dict is correctly generated + ansible.builtin.assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/parsed.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/parsed.yaml new file mode 100644 index 000000000..28a113c75 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/parsed.yaml @@ -0,0 +1,9 @@ +--- +- ansible.builtin.debug: + msg: START iosxr_vrf_global parsed integration tests on connection={{ ansible_connection }} + +- name: Parse externally provided VRF configuration + register: result + cisco.iosxr.iosxr_vrf_global: + running_config: "{{ lookup('file', './_parsed.cfg') }}" + state: parsed diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/purged.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/purged.yaml new file mode 100644 index 000000000..d45b570a4 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/purged.yaml @@ -0,0 +1,41 @@ +--- +- ansible.builtin.debug: + msg: Start iosxr_vrf_global purged integration tests connection={{ ansible_connection}} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- ansible.builtin.include_tasks: _populate.yaml + +- block: + - name: Purge all VRF configuration from the device + cisco.iosxr.iosxr_vrf_global: &id001 + state: purged + register: result + + - name: Assert that before dicts are correctly generated + ansible.builtin.assert: + that: + - merged['after'] == result['before'] + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "'no vrf VRF4' in result.commands" + - result.commands|length == 1 + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - result['after'] == [] + + - name: Purge all VRF configuration from the device (idempotent) + register: result + cisco.iosxr.iosxr_vrf_global: *id001 + - name: Assert that task was idempotent + ansible.builtin.assert: + that: + - result['changed'] == false + - result.commands|length == 0 + + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/rendered.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/rendered.yaml new file mode 100644 index 000000000..f80e2f5f9 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/rendered.yaml @@ -0,0 +1,28 @@ +--- +- ansible.builtin.debug: + msg: START iosxr_vrf_global rendered integration tests on connection={{ ansible_connection }} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- name: Render provided configuration with device configuration + cisco.iosxr.iosxr_vrf_global: + config: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: merged-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "3:4" + remote_route_filtering: + disable: true + vpn: + id: "2:3" + state: rendered + register: result + +- name: Assert that correct set of commands were rendered + ansible.builtin.assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['rendered']) |length == 0 }}" diff --git a/tests/integration/targets/iosxr_vrf_global/tests/common/replaced.yaml b/tests/integration/targets/iosxr_vrf_global/tests/common/replaced.yaml new file mode 100644 index 000000000..edfe92475 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/tests/common/replaced.yaml @@ -0,0 +1,51 @@ +--- +- ansible.builtin.debug: + msg: Start iosxr_vrf_global replaced integration tests connection={{ ansible_connection}} + +- ansible.builtin.include_tasks: _remove_config.yaml + +- ansible.builtin.include_tasks: _populate.yaml + +- block: + - name: Replace given vrf configuration with provided configurations + cisco.iosxr.iosxr_vrf_global: &replaced + config: + - name: VRF7 + description: VRF7 description + evpn_route_sync: 398 + fallback_vrf: "replaced-vrf" + remote_route_filtering: + disable: true + rd: "6:9" + mhost: + afi: "ipv4" + default_interface: "Loopback0" + vpn: + id: "4:5" + state: replaced + register: result + + - name: Assert that correct set of commands were generated + ansible.builtin.assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}" + + - name: Assert that before dict is correctly generated + ansible.builtin.assert: + that: + - "{{ replaced['before'] == result['before'] }}" + + - name: Assert that after dict is correctly generated + ansible.builtin.assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Idempotency check + register: result + cisco.iosxr.iosxr_vrf_global: *replaced + - ansible.builtin.assert: + that: + - result['changed'] == false + - result.commands|length == 0 + always: + - ansible.builtin.include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/iosxr_vrf_global/vars/main.yaml b/tests/integration/targets/iosxr_vrf_global/vars/main.yaml new file mode 100644 index 000000000..6e7baf6c6 --- /dev/null +++ b/tests/integration/targets/iosxr_vrf_global/vars/main.yaml @@ -0,0 +1,178 @@ +--- +merged: + before: [] + + commands: + - vrf VRF4 + - description VRF4 Description + - evpn-route-sync 793 + - fallback-vrf merged-vrf + - mhost ipv4 default-interface Loopback0 + - rd 3:4 + - remote-route-filtering disable + - vpn id 2:3 + after: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: merged-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "3:4" + remote_route_filtering: + disable: true + vpn: + id: "2:3" + +replaced: + before: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: merged-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "3:4" + remote_route_filtering: + disable: true + vpn: + id: "2:3" + commands: + - vrf VRF7 + - description VRF7 description + - evpn-route-sync 398 + - fallback-vrf replaced-vrf + - mhost ipv4 default-interface Loopback0 + - rd 6:9 + - remote-route-filtering disable + - vpn id 4:5 + after: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: merged-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "3:4" + remote_route_filtering: + disable: true + vpn: + id: "2:3" + - name: VRF7 + description: VRF7 description + evpn_route_sync: 398 + fallback_vrf: "replaced-vrf" + remote_route_filtering: + disable: true + rd: "6:9" + mhost: + afi: "ipv4" + default_interface: "Loopback0" + vpn: + id: "4:5" + +overridden: + before: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: merged-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "3:4" + remote_route_filtering: + disable: true + vpn: + id: "2:3" + + commands: + - vrf VRF4 + - no description VRF4 Description + - no evpn-route-sync 793 + - no fallback-vrf merged-vrf + - no mhost ipv4 default-interface Loopback0 + - no rd 3:4 + - no remote-route-filtering disable + - no vpn id 2:3 + - vrf VRF6 + - description VRF6 Description + - evpn-route-sync 101 + - fallback-vrf overridden-vrf + - mhost ipv4 default-interface Loopback0 + - rd 9:8 + - remote-route-filtering disable + - vpn id 23:3 + after: + - name: VRF4 + - name: VRF6 + description: VRF6 Description + evpn_route_sync: 101 + fallback_vrf: overridden-vrf + remote_route_filtering: + disable: true + rd: "9:8" + mhost: + afi: "ipv4" + default_interface: "Loopback0" + vpn: + id: "23:3" + +deleted: + before: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: merged-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "3:4" + remote_route_filtering: + disable: true + vpn: + id: "2:3" + commands: + - vrf VRF4 + - no description VRF4 Description + - no evpn-route-sync 793 + - no fallback-vrf merged-vrf + - no mhost ipv4 default-interface Loopback0 + - no rd 3:4 + - no remote-route-filtering disable + - no vpn id 2:3 + after: + - name: VRF4 + +gathered: + after: + - name: VRF4 + description: VRF4 Description + evpn_route_sync: 793 + fallback_vrf: merged-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "3:4" + remote_route_filtering: + disable: true + vpn: + id: "2:3" + +parsed: + after: + - name: test + description: "This is test VRF" + evpn_route_sync: 456 + fallback_vrf: test-vrf + mhost: + afi: "ipv4" + default_interface: "Loopback0" + rd: "testing" + remote_route_filtering: + disable: true + vpn: + id: "56" diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.18.txt similarity index 60% rename from tests/sanity/ignore-2.14.txt rename to tests/sanity/ignore-2.18.txt index 1e8023a57..687c8ab32 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.18.txt @@ -1,4 +1,3 @@ plugins/action/iosxr.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -plugins/sub_plugins/grpc/pb/ems_grpc_pb2.py import-3.9!skip -plugins/sub_plugins/grpc/pb/ems_grpc_pb2.py import-3.10!skip plugins/sub_plugins/grpc/pb/ems_grpc_pb2.py import-3.11!skip +plugins/sub_plugins/grpc/pb/ems_grpc_pb2.py import-3.12!skip diff --git a/tests/unit/modules/network/iosxr/test_iosxr.py b/tests/unit/modules/network/iosxr/test_iosxr.py index 32eb85797..b54684bb4 100644 --- a/tests/unit/modules/network/iosxr/test_iosxr.py +++ b/tests/unit/modules/network/iosxr/test_iosxr.py @@ -23,9 +23,9 @@ from os import path from unittest import TestCase +from unittest.mock import MagicMock from ansible.module_utils._text import to_bytes, to_text -from mock import MagicMock from ansible_collections.cisco.iosxr.plugins.cliconf import iosxr diff --git a/tests/unit/modules/network/iosxr/test_iosxr_n540.py b/tests/unit/modules/network/iosxr/test_iosxr_n540.py index e7ec26877..4ac2920bb 100644 --- a/tests/unit/modules/network/iosxr/test_iosxr_n540.py +++ b/tests/unit/modules/network/iosxr/test_iosxr_n540.py @@ -23,9 +23,9 @@ from os import path from unittest import TestCase +from unittest.mock import MagicMock from ansible.module_utils._text import to_bytes, to_text -from mock import MagicMock from ansible_collections.cisco.iosxr.plugins.cliconf import iosxr diff --git a/tests/unit/modules/network/iosxr/test_iosxr_ping.py b/tests/unit/modules/network/iosxr/test_iosxr_ping.py index b5701c655..87ffc4f13 100644 --- a/tests/unit/modules/network/iosxr/test_iosxr_ping.py +++ b/tests/unit/modules/network/iosxr/test_iosxr_ping.py @@ -135,7 +135,7 @@ def test_iosxr_ping_state_absent_pass(self): } self.assertEqual(result, mock_res) - def test_iosxr_ping_state_absent_pass(self): + def test_iosxr_ping_state_absent_pass_1(self): self.execute_show_command.return_value = dedent( """\ Type escape sequence to abort. diff --git a/tests/unit/modules/network/iosxr/test_iosxr_vrf_global.py b/tests/unit/modules/network/iosxr/test_iosxr_vrf_global.py new file mode 100644 index 000000000..a25556a48 --- /dev/null +++ b/tests/unit/modules/network/iosxr/test_iosxr_vrf_global.py @@ -0,0 +1,524 @@ +# (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 . + +# Make coding more python3-ish + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from textwrap import dedent +from unittest.mock import patch + +from ansible_collections.cisco.iosxr.plugins.modules import iosxr_vrf_global +from ansible_collections.cisco.iosxr.tests.unit.modules.utils import set_module_args + +from .iosxr_module import TestIosxrModule + + +class TestIosxrVrfGlobalModule(TestIosxrModule): + """Tests the iosxr_vrf_global module.""" + + module = iosxr_vrf_global + + def setUp(self): + """Setup for iosxr_vrfs module tests.""" + super(TestIosxrVrfGlobalModule, self).setUp() + + self.mock_get_resource_connection = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection = self.mock_get_resource_connection.start() + + self.mock_get_config = patch( + "ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.facts.vrf_global.vrf_global." + "Vrf_globalFacts.get_config", + ) + self.get_config = self.mock_get_config.start() + + def tearDown(self): + """Tear down for iosxr_vrfs module tests.""" + super(TestIosxrVrfGlobalModule, self).tearDown() + self.get_resource_connection.stop() + self.get_config.stop() + + def test_iosxr_vrf_global_merged_idempotent(self): + """Test the idempotent nature of the iosxr_vrf_global module in merged state.""" + run_cfg = dedent( + """\ + vrf test + mhost ipv4 default-interface Loopback0 + evpn-route-sync 235 + description "this is sample vrf for feature testing" + fallback-vrf "parsed-vrf" + rd "2:3" + remote-route-filtering disable + vpn id 23 + """, + ) + self.get_config.return_value = run_cfg + set_module_args( + dict( + config=[ + dict( + name="test", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + evpn_route_sync=235, + description="this is sample vrf for feature testing", + fallback_vrf="parsed-vrf", + rd="2:3", + remote_route_filtering=dict(disable=True), + vpn=dict(id="23"), + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_iosxr_vrf_global_merged(self): + """Test the merged state of the iosxr_vrf_global module.""" + set_module_args( + dict( + config=[ + dict( + name="VRF4", + description="VRF4 Description", + evpn_route_sync=793, + fallback_vrf="parsed-vrf", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + rd="3:4", + remote_route_filtering=dict(disable=True), + vpn=dict(id="23"), + ), + ], + state="merged", + ), + ) + commands = [ + "vrf VRF4", + "description VRF4 Description", + "evpn-route-sync 793", + "fallback-vrf parsed-vrf", + "mhost ipv4 default-interface Loopback0", + "rd 3:4", + "remote-route-filtering disable", + "vpn id 23", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_iosxr_vrf_global_replaced(self): + """Test the replaced state of the iosxr_vrf_global module.""" + run_cfg = dedent( + """\ + vrf VRF4 + mhost ipv4 default-interface Loopback0 + evpn-route-sync 793 + description VRF4 description + vpn id 23 + fallback-vrf parsed-vrf + remote-route-filtering disable + rd 3:4 + """, + ) + self.get_config.return_value = run_cfg + + set_module_args( + dict( + config=[ + dict( + name="VRF4", + description="VRF4 description", + evpn_route_sync=793, + fallback_vrf="parsed-vrf", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + rd="3:4", + remote_route_filtering=dict(disable=True), + vpn=dict(id="23"), + ), + dict( + name="VRF7", + description="VRF7 description", + evpn_route_sync=398, + fallback_vrf="replaced-vrf", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + rd="67:9", + remote_route_filtering=dict(disable=True), + vpn=dict(id="4:5"), + ), + ], + state="replaced", + ), + ) + commands = [ + "vrf VRF7", + "description VRF7 description", + "evpn-route-sync 398", + "fallback-vrf replaced-vrf", + "mhost ipv4 default-interface Loopback0", + "rd 67:9", + "remote-route-filtering disable", + "vpn id 4:5", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_iosxr_vrf_global_replaced_idempotent(self): + """Test the idempotent nature of the iosxr_vrf_global module in replaced state.""" + run_cfg = dedent( + """\ + vrf VRF4 + mhost ipv4 default-interface Loopback0 + evpn-route-sync 793 + description VRF4 description + vpn id 23 + fallback-vrf parsed-vrf + remote-route-filtering disable + rd 3:4 + vrf VRF7 + mhost ipv4 default-interface Loopback0 + evpn-route-sync 398 + description VRF7 description + vpn id 4:5 + fallback-vrf replaced-vrf + remote-route-filtering disable + rd 67:9 + """, + ) + self.get_config.return_value = run_cfg + + set_module_args( + dict( + config=[ + dict( + name="VRF4", + description="VRF4 description", + evpn_route_sync=793, + fallback_vrf="parsed-vrf", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + rd="3:4", + remote_route_filtering=dict(disable=True), + vpn=dict(id="23"), + ), + dict( + name="VRF7", + description="VRF7 description", + evpn_route_sync=398, + fallback_vrf="replaced-vrf", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + rd="67:9", + remote_route_filtering=dict(disable=True), + vpn=dict(id="4:5"), + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_iosxr_vrf_global_overridden(self): + """Test the overridden state of the iosxr_vrf_global module.""" + run_cfg = dedent( + """\ + vrf VRF4 + mhost ipv4 default-interface Loopback0 + evpn-route-sync 793 + description VRF4 description + vpn id 2:3 + fallback-vrf merged-vrf + remote-route-filtering disable + rd 3:4 + vrf VRF7 + mhost ipv4 default-interface Loopback0 + evpn-route-sync 398 + description VRF7 description + vpn id 4:5 + fallback-vrf replaced-vrf + remote-route-filtering disable + rd 6:9 + """, + ) + self.get_config.return_value = run_cfg + + set_module_args( + dict( + config=[ + dict( + name="VRF4", + ), + dict( + name="VRF6", + description="VRF6 description", + evpn_route_sync=101, + fallback_vrf="overridden-vrf", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + rd="9:8", + remote_route_filtering=dict(disable=True), + vpn=dict(id="23:3"), + ), + dict( + name="VRF7", + ), + ], + state="overridden", + ), + ) + commands = [ + "vrf VRF4", + "no description VRF4 description", + "no evpn-route-sync 793", + "no fallback-vrf merged-vrf", + "no mhost ipv4 default-interface Loopback0", + "no rd 3:4", + "no remote-route-filtering disable", + "no vpn id 2:3", + "vrf VRF7", + "no description VRF7 description", + "no evpn-route-sync 398", + "no fallback-vrf replaced-vrf", + "no mhost ipv4 default-interface Loopback0", + "no rd 6:9", + "no remote-route-filtering disable", + "no vpn id 4:5", + "vrf VRF6", + "description VRF6 description", + "evpn-route-sync 101", + "fallback-vrf overridden-vrf", + "mhost ipv4 default-interface Loopback0", + "rd 9:8", + "remote-route-filtering disable", + "vpn id 23:3", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_iosxr_vrf_global_overridden_idempotent(self): + """Test the idempotent nature of the iosxr_vrf_global module in overridden state.""" + run_cfg = dedent( + """\ + vrf VRF4 + vrf VRF6 + mhost ipv4 default-interface Loopback0 + evpn-route-sync 101 + description VRF6 description + vpn id 4:5 + fallback-vrf overridden-vrf + remote-route-filtering disable + rd 67:9 + vrf VRF7 + """, + ) + self.get_config.return_value = run_cfg + + set_module_args( + dict( + config=[ + dict( + name="VRF4", + ), + dict( + name="VRF6", + description="VRF6 description", + evpn_route_sync=101, + fallback_vrf="overridden-vrf", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + rd="67:9", + remote_route_filtering=dict(disable=True), + vpn=dict(id="4:5"), + ), + dict( + name="VRF7", + ), + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_iosxr_vrf_global_deleted(self): + """Test the deleted state of the iosxr_vrf_global module.""" + run_cfg = dedent( + """\ + vrf VRF4 + vrf VRF6 + mhost ipv4 default-interface Loopback0 + evpn-route-sync 101 + description VRF6 description + vpn id 23:3 + fallback-vrf overridden-vrf + remote-route-filtering disable + rd 9:8 + vrf VRF7 + """, + ) + self.get_config.return_value = run_cfg + set_module_args( + dict( + config=[ + dict( + name="VRF4", + ), + dict( + name="VRF6", + ), + dict( + name="VRF7", + ), + ], + state="deleted", + ), + ) + + commands = [ + "vrf VRF6", + "no description VRF6 description", + "no evpn-route-sync 101", + "no fallback-vrf overridden-vrf", + "no mhost ipv4 default-interface Loopback0", + "no rd 9:8", + "no remote-route-filtering disable", + "no vpn id 23:3", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_iosxr_vrf_global_deleted_idempotent(self): + """Test the idempotent nature of the iosxr_vrf_global module in deleted state.""" + run_cfg = dedent( + """\ + """, + ) + self.get_config.return_value = run_cfg + set_module_args(dict(config=[], state="deleted")) + + result = self.execute_module(changed=False) + self.assertEqual(result["commands"], []) + + def test_ios_vrf_global_purged(self): + """Test the purged state of the iosxr_vrf_global module.""" + run_cfg = dedent( + """\ + vrf VRF4 + vrf VRF6 + vrf VRF7 + """, + ) + self.get_config.return_value = run_cfg + set_module_args(dict(state="purged")) + commands = [ + "no vrf VRF4", + "no vrf VRF6", + "no vrf VRF7", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_iosxr_vrf_global_rendered(self): + """Test the rendered state of the iosxr_vrf_global module.""" + set_module_args( + dict( + config=[ + dict( + name="VRF4", + description="VRF4 Description", + evpn_route_sync=793, + fallback_vrf="rendered-vrf", + mhost=dict( + afi="ipv4", + default_interface="Loopback0", + ), + rd="3:4", + remote_route_filtering=dict(disable=True), + vpn=dict(id="2:3"), + ), + ], + state="rendered", + ), + ) + commands = [ + "vrf VRF4", + "description VRF4 Description", + "evpn-route-sync 793", + "fallback-vrf rendered-vrf", + "mhost ipv4 default-interface Loopback0", + "rd 3:4", + "remote-route-filtering disable", + "vpn id 2:3", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) + + def test_iosxr_vrf_global_parsed(self): + """Test the parsed state of the iosxr_vrf_global module.""" + run_cfg = dedent( + """\ + vrf my_vrf + mhost ipv4 default-interface Loopback0 + evpn-route-sync 235 + description "this is sample vrf for feature testing" + fallback-vrf "parsed-vrf" + rd "2:3" + remote-route-filtering disable + """, + ) + set_module_args(dict(running_config=run_cfg, state="parsed")) + result = self.execute_module(changed=False) + parsed_list = [ + { + "name": "my_vrf", + "mhost": { + "afi": "ipv4", + "default_interface": "Loopback0", + }, + "evpn_route_sync": 235, + "description": "this is sample vrf for feature testing", + "fallback_vrf": "parsed-vrf", + "rd": "2:3", + "remote_route_filtering": { + "disable": True, + }, + }, + ] + + self.assertEqual(parsed_list, result["parsed"]) diff --git a/tox-ansible.ini b/tox-ansible.ini index 5e1f4b36a..b49a359f9 100644 --- a/tox-ansible.ini +++ b/tox-ansible.ini @@ -1,10 +1,3 @@ [ansible] -skip = - py3.7 - py3.8 - 2.9 - 2.10 - 2.11 - 2.12 - 2.13 +skip = "" diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 7dbe41e8f..000000000 --- a/tox.ini +++ /dev/null @@ -1,31 +0,0 @@ -[tox] -minversion = 1.4.2 -envlist = linters -skipsdist = True - -[testenv] -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -[testenv:black] -install_command = pip install {opts} {packages} -commands = - black -v {toxinidir} - -[testenv:linters] -install_command = pip install {opts} {packages} -commands = - black -v --diff --check {toxinidir} - flake8 {posargs} - -[testenv:venv] -commands = {posargs} - -[flake8] -# E123, E125 skipped as they are invalid PEP-8. - -show-source = True -ignore = E123,E125,E203,E402,E501,E741,F401,F811,F841,W503 -max-line-length = 160 -builtins = _ -exclude = .git,.tox