diff --git a/plugins/modules/dcnm_interface.py b/plugins/modules/dcnm_interface.py index 983e443eb..946d40d94 100644 --- a/plugins/modules/dcnm_interface.py +++ b/plugins/modules/dcnm_interface.py @@ -1882,6 +1882,11 @@ find_dict_in_list_by_key_value, ) from ..module_utils.common.log_v2 import Log +from ..module_utils.common.controller_version_v2 import ControllerVersion +from ..module_utils.common.rest_send_v2 import RestSend +from ..module_utils.common.response_handler import ResponseHandler +from ..module_utils.common.sender_dcnm import Sender +from ..module_utils.common.exceptions import ControllerResponseError def json_pretty(msg): @@ -1968,6 +1973,7 @@ def __init__(self, module): ] self.dcnm_version = dcnm_version_supported(self.module) + self.ndfc_version = self._get_ndfc_version() self.inventory_data = {} self.manageable = [] @@ -2062,6 +2068,12 @@ def __init__(self, module): } + # NDFC 12.4.1+ (ND 4.1.1+) parameters + if self._ndfc_version_gte("12.4.1"): + self.keymap.update({ + "FEC": "fec", + }) + # New Interfaces self.pol_types = { 11: { @@ -2161,6 +2173,35 @@ def __init__(self, module): msg = "ENTERED DcnmIntf: " self.log.debug(msg) + def _get_ndfc_version(self): + """Return the full NDFC version string (e.g. '12.4.1.245') using ControllerVersion, or None on failure.""" + try: + sender = Sender() + sender.ansible_module = self.module + rest_send = RestSend(self.module.params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + controller_version = ControllerVersion() + controller_version.rest_send = rest_send + controller_version.refresh() + raw_version = controller_version.version + if raw_version: + return re.sub(r'[a-zA-Z]+$', '', raw_version) + except (ControllerResponseError, ValueError) as error: + self.log.warning("Unable to determine NDFC version: %s", error) + return None + + def _ndfc_version_gte(self, target): + """Check if NDFC version >= target. Uses tuple comparison on version segments.""" + if not self.ndfc_version: + return False + try: + current = tuple(int(x) for x in self.ndfc_version.split(".")[:3]) + required = tuple(int(x) for x in target.split(".")[:3]) + return current >= required + except (ValueError, AttributeError): + return False + def dcnm_intf_breakout_format(self, if_name): # Define the pattern to match '1/x/y' where x and y are integers pattern = r'^ethernet1/\d+/\d+$' @@ -2676,6 +2717,11 @@ def dcnm_intf_validate_ethernet_interface_input(self, cfg): queuing_policy=dict(type="str", default=""), ) + if self._ndfc_version_gte("12.4.1"): + eth_prof_spec_trunk.update({ + "fec": dict(type="str", default="auto", choices=["auto", "fc-fec", "off", "rs-cons16", "rs-fec", "rs-ieee"]), + }) + eth_prof_spec_access = dict( mode=dict(required=True, type="str"), bpdu_guard=dict(type="str", default="true"), @@ -2699,6 +2745,11 @@ def dcnm_intf_validate_ethernet_interface_input(self, cfg): queuing_policy=dict(type="str", default=""), ) + if self._ndfc_version_gte("12.4.1"): + eth_prof_spec_access.update({ + "fec": dict(type="str", default="auto", choices=["auto", "fc-fec", "off", "rs-cons16", "rs-fec", "rs-ieee"]), + }) + eth_prof_spec_routed_host = dict( int_vrf=dict(type="str", default="default"), ipv4_addr=dict(type="ipv4", default=""), @@ -2714,6 +2765,11 @@ def dcnm_intf_validate_ethernet_interface_input(self, cfg): queuing_policy=dict(type="str", default=""), ) + if self._ndfc_version_gte("12.4.1"): + eth_prof_spec_routed_host.update({ + "fec": dict(type="str", default="auto", choices=["auto", "fc-fec", "off", "rs-cons16", "rs-fec", "rs-ieee"]), + }) + eth_prof_spec_epl_routed_host = dict( mode=dict(required=True, type="str"), ipv4_addr=dict(required=True, type="ipv4"), @@ -2746,6 +2802,11 @@ def dcnm_intf_validate_ethernet_interface_input(self, cfg): type="str", default="auto", choices=["auto", "full", "half"]), ) + if self._ndfc_version_gte("12.4.1"): + eth_prof_spec_dot1q_tunnel_host.update({ + "fec": dict(type="str", default="auto", choices=["auto", "fc-fec", "off", "rs-cons16", "rs-fec", "rs-ieee"]), + }) + if "trunk" == cfg[0]["profile"]["mode"]: self.dcnm_intf_validate_interface_input( cfg, eth_spec, eth_prof_spec_trunk @@ -3565,6 +3626,8 @@ def dcnm_intf_get_eth_payload(self, delem, intf, profile): intf["interfaces"][0]["nvPairs"]["QUEUING_POLICY"] = delem[profile]["queuing_policy"] else: intf["interfaces"][0]["nvPairs"]["QUEUING_POLICY"] = "" + if self._ndfc_version_gte("12.4.1"): + intf["interfaces"][0]["nvPairs"]["FEC"] = delem[profile].get("fec", "auto") if delem[profile]["mode"] == "access": intf["interfaces"][0]["nvPairs"]["BPDUGUARD_ENABLED"] = delem[ profile @@ -3602,6 +3665,8 @@ def dcnm_intf_get_eth_payload(self, delem, intf, profile): intf["interfaces"][0]["nvPairs"]["QUEUING_POLICY"] = delem[profile]["queuing_policy"] else: intf["interfaces"][0]["nvPairs"]["QUEUING_POLICY"] = "" + if self._ndfc_version_gte("12.4.1"): + intf["interfaces"][0]["nvPairs"]["FEC"] = delem[profile].get("fec", "auto") if delem[profile]["mode"] == "routed": intf["interfaces"][0]["nvPairs"]["INTF_VRF"] = delem[profile][ "int_vrf" @@ -3635,6 +3700,8 @@ def dcnm_intf_get_eth_payload(self, delem, intf, profile): intf["interfaces"][0]["nvPairs"]["QUEUING_POLICY"] = delem[profile]["queuing_policy"] else: intf["interfaces"][0]["nvPairs"]["QUEUING_POLICY"] = "" + if self._ndfc_version_gte("12.4.1"): + intf["interfaces"][0]["nvPairs"]["FEC"] = delem[profile].get("fec", "auto") if delem[profile]["mode"] == "monitor": intf["interfaces"][0]["nvPairs"]["INTF_NAME"] = ifname if delem[profile]["mode"] == "epl_routed": @@ -3692,6 +3759,8 @@ def dcnm_intf_get_eth_payload(self, delem, intf, profile): intf["interfaces"][0]["nvPairs"]["INTF_NAME"] = ifname intf["interfaces"][0]["nvPairs"][ "PORT_DUPLEX_MODE"] = delem[profile]["duplex"] + if self._ndfc_version_gte("12.4.1"): + intf["interfaces"][0]["nvPairs"]["FEC"] = delem[profile].get("fec", "auto") def dcnm_intf_get_st_fex_payload(self, delem, intf, profile): @@ -4565,6 +4634,9 @@ def dcnm_intf_compare_want_and_have(self, state): "QUEUING_POLICY" ] + if self._ndfc_version_gte("12.4.1"): + keys_to_check.append("FEC") + for key in keys_to_check: # Remove the key from nv_keys only if it exists and is not present in 'have' if key in nv_keys and d[k][index][ik].get(key, None) is None: