Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resource Module: VRF address family #902

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ Name | Description
[cisco.nxos.nxos_vpc](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vpc_module.rst)|Manages global VPC configuration
[cisco.nxos.nxos_vpc_interface](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vpc_interface_module.rst)|Manages interface VPC configuration
[cisco.nxos.nxos_vrf](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_module.rst)|(deprecated, removed after 2026-07-25) Manages global VRF configuration.
[cisco.nxos.nxos_vrf_address_family](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_address_family_module.rst)|Resource module to configure VRF definitions.
[cisco.nxos.nxos_vrf_af](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_af_module.rst)|Manages VRF AF.
[cisco.nxos.nxos_vrf_global](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_global_module.rst)|Resource module to configure VRF definitions.
[cisco.nxos.nxos_vrf_interface](https://github.com/ansible-collections/cisco.nxos/blob/main/docs/cisco.nxos.nxos_vrf_interface_module.rst)|Manages interface specific VRF configuration.
Expand Down
1 change: 1 addition & 0 deletions plugins/action/vrf_address_family.py
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# -*- 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 nxos_vrf_address_family module
"""


class Vrf_address_familyArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_vrf_address_family module"""

argument_spec = {
"config": {
"type": "list",
"elements": "dict",
"options": {
"name": {"type": "str", "required": True},
"address_families": {
"type": "list",
"elements": "dict",
"options": {
"afi": {"type": "str", "choices": ["ipv4", "ipv6"]},
"safi": {
"type": "str",
"choices": ["multicast", "unicast"],
},
"maximum": {
"type": "dict",
"options": {
"max_routes": {"type": "int"},
"max_route_options": {
"type": "dict",
"mutually_exclusive": [
["warning_only", "threshold"],
],
"options": {
"warning_only": {"type": "bool"},
"threshold": {
"type": "dict",
"options": {
"threshold_value": {"type": "int"},
"reinstall_threshold": {
"type": "int",
},
},
},
},
},
},
},
"route_target": {
"type": "list",
"elements": "dict",
"mutually_exclusive": [["import", "export"]],
"options": {
"import": {"type": "str"},
"export": {"type": "str"},
},
},
"export": {
"type": "list",
"elements": "dict",
"mutually_exclusive": [["map", "vrf"]],
"options": {
"map": {"type": "str"},
"vrf": {
"type": "dict",
"options": {
"max_prefix": {"type": "int"},
"map_import": {"type": "str"},
"allow_vpn": {"type": "bool"},
},
},
},
},
"import": {
"type": "list",
"elements": "dict",
"mutually_exclusive": [["map", "vrf"]],
"options": {
"map": {"type": "str"},
"vrf": {
"type": "dict",
"options": {
"max_prefix": {"type": "int"},
"map_import": {"type": "str"},
"advertise_vpn": {"type": "bool"},
},
},
},
},
},
},
},
},
"running_config": {"type": "str"},
"state": {
"choices": [
"parsed",
"gathered",
"deleted",
"merged",
"replaced",
"rendered",
"overridden",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
#
# -*- 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 nxos_vrf_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.vrf_address_family import (
Vrf_address_familyTemplate,
)


class Vrf_address_family(ResourceModule):
"""
The nxos_vrf_address_family config class
"""

def __init__(self, module):
super(Vrf_address_family, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="vrf_address_family",
tmplt=Vrf_address_familyTemplate(),
)
self.parsers = [
"maximum",
]
self.list_parsers = [
"route_target.import",
"route_target.export",
"export.map",
"export.vrf",
"import.map",
"import.vrf",
]

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._vrf_address_family_list_to_dict(self.want)
haved = self._vrf_address_family_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 = self._filter_have_to_want(haved, 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, vrf=k)

for k, want in iteritems(wantd):
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_address_family network resource.
"""
begin = len(self.commands)
self._compare_vrf_afs(want=want, have=have)
if len(self.commands) != begin:
self.commands.insert(begin, self._tmplt.render({"name": vrf}, "name", False))

def _compare_vrf_afs(self, want, have):
"""Compare the VRF address families lists.
:params want: the want VRF dictionary
:params have: the have VRF dictionary
"""
waafs = want.get("address_families", {})
haafs = have.get("address_families", {})

address_fam_list = [
("ipv4", "unicast"),
("ipv6", "unicast"),
("ipv4", "multicast"),
("ipv6", "multicast"),
]

for item in address_fam_list:
begin = len(self.commands)
for afk, afv in iteritems(waafs):
if afv.get("afi", "") == item[0] and afv.get("safi", "") == item[1]:
self._compare_single_af(wantaf=afv, haveaf=haafs.pop(afk, {}))
for afk, afv in iteritems(haafs):
if afv.get("afi", "") == item[0] and afv.get("safi", "") == item[1]:
self._compare_single_af(wantaf={}, haveaf=afv)
if len(self.commands) != begin:
self.commands.insert(
begin,
self._tmplt.render(
{"afi": item[0], "safi": item[1]},
"address_family",
False,
),
)

def _compare_single_af(self, wantaf, haveaf):
"""Compare a single address family.
:params want: the want address family dictionary
:params have: the have address family dictionary
"""
self.compare(parsers=self.parsers, want=wantaf, have=haveaf)
self._compare_af_lists(want=wantaf, have=haveaf)

def _compare_af_lists(self, want, have):
"""Compare single vrf af list items.
:params want: the want list item dictionary
:params have: the have list item dictionary
"""

for attrib in self.list_parsers:
parser_split = attrib.split(".")
wdict = self._convert_to_dict(want.get(parser_split[0], []), parser_split[1])
hdict = self._convert_to_dict(have.get(parser_split[0], []), parser_split[1])

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 _convert_to_dict(self, vrf_af_item: list, parser_item: str) -> dict:
"""Convert to dict based on parser name.

:params vrf_af_item: the vrf af item
:params parser_item: the parser name based on which it needs to be converted
:returns: A dictionary with items that have the key parser_item
"""
if not vrf_af_item:
return {}

result = {}
for item in vrf_af_item:
if parser_item in item:
if parser_item == "vrf":
vrf_item = item.get("vrf", {})
key = f"vrf_{vrf_item.get('max_prefix', 'noprefix')}_{vrf_item.get('map_import', 'nomap')}"
else:
key = item[parser_item]
result[key] = item
return result

def _filter_have_to_want(self, haved, wantd):
if isinstance(haved, dict) and isinstance(wantd, dict):
filtered = {
k: self._filter_have_to_want(haved[k], wantd[k]) for k in haved if k in wantd
}
return {k: v for k, v in filtered.items() if v not in [None, {}, []]}
elif isinstance(haved, list) and isinstance(wantd, list):
filtered_list = []
for h_item in haved:
if isinstance(h_item, dict):
for w_item in wantd:
filtered_item = self._filter_have_to_want(h_item, w_item)
if filtered_item not in [None, {}, []]:
filtered_list.append(filtered_item)
break
else:
if h_item in wantd:
filtered_list.append(h_item)
return filtered_list
else:
return haved if haved == wantd else None

def _vrf_address_family_list_to_dict(self, vrf_af_list: list) -> dict:
"""Convert a list of vrf_address_family dictionaries to a dictionary.

:param vrf_af_list: A list of vrf_address_family dictionaries.
:type vrf_af_list: list
:rtype: dict
:returns: A dictionary of vrf_address_family dictionaries.
"""

items = {}
for af_item in vrf_af_list:
name = af_item.get("name")
address_families = af_item.get("address_families", [])
item = {
"name": name,
"address_families": {
f"{name}_{af.get('afi')}_{af.get('safi')}": af for af in address_families
},
}

items[name] = item
return items
4 changes: 4 additions & 0 deletions plugins/module_utils/network/nxos/facts/facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@
from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vlans.vlans import (
VlansFacts,
)
from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vrf_address_family.vrf_address_family import (
Vrf_address_familyFacts,
)
from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vrf_global.vrf_global import (
Vrf_globalFacts,
)
Expand Down Expand Up @@ -154,6 +157,7 @@
hostname=HostnameFacts,
bgp_templates=Bgp_templatesFacts,
vrf_global=Vrf_globalFacts,
vrf_address_family=Vrf_address_familyFacts,
)
MDS_FACT_RESOURCE_SUBSETS = dict(
fc_interfaces=Fc_interfacesFacts,
Expand Down
Empty file.
Loading
Loading