From 40d93d6f293c2983763039ddbba2fb5014641f60 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 2 Dec 2024 19:32:37 -1000 Subject: [PATCH 01/49] Tentative fix for Issue #351 The fix entails a modification to wait_for_vrf_del_ready() In both the legitimate case (user trying to delete a VRF after having removed all network attachments) `lanAttachState` very briefly transitions to DEPLOY before transitioning to its final state of NA. However, in this case, `isLanAttached` (in the same data structure) is False. Whereas in the illegitimate case (user hasn't removed network attachments) `isLanAttached` is True. Hence, we can leverage `isLanAttached` to differentiate between legitimate and illegitimate cases. Adding another conditional that checks if `lanAttachState` == DEPLOY AND `isLanAttached` == True. If this is the case, then the user is trying to delete a VRF that still contains network attachments and we now fail immediately with an appropriate error message. Other changes: 1. Add standard python logging 2. Use `ControllerVersion()` to retrieve the NDFC version and remove import for `dcnm_version_supported` 3. Use `FabricDetails()` to retrieve fabric type. 4. Modify `update_attach_params()` to improve readability by first populating the neighbor dictionary before appending it. This way, we avoid a lot of unsightly accesses to element 0 of the list. For example: ```python if a_l["peer_vrf"]: vrflite_con["VRF_LITE_CONN"][0]["PEER_VRF_NAME"] = a_l["peer_vrf"] else: vrflite_con["VRF_LITE_CONN"][0]["PEER_VRF_NAME"] = "" ``` Becomes: ```python if a_l["peer_vrf"]: nbr_dict["PEER_VRF_NAME"] = a_l["peer_vrf"] else: nbr_dict["PEER_VRF_NAME"] = "" ``` 5. diff_for_attach_deploy() - Reduce indentation by reversing logic of conditional. The following: ```python if wlite["IF_NAME"] == hlite["IF_NAME"]: # Lots of indented code ... ``` Becomes: ```python if wlite["IF_NAME"] != hlite["IF_NAME"]: continue # unindent the above code ``` 6. get_have() - Reduce indentation levels by reversing logic (similar to #5 above) 7. Add method want_and_have_vrf_template_configs_differ(), see next item. 8. diff_for_create() - Leverage want_and_have_vrf_template_configs_differ() to simplify. 9. Add method to_bool(), see next item 10. diff_for_attach_deploy() - Simplify/shorten by leveraging to_bool() 11. In multiple places, ensure that a key exists before accessing it or deleting it. 12. Run though black 13. Several minor formatting changes for improved readability. --- plugins/modules/dcnm_vrf.py | 1082 ++++++++++++++++++++--------------- 1 file changed, 618 insertions(+), 464 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index f8c5e4645..df7284ea5 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -20,6 +20,27 @@ "Shrishail Kariyappanavar, Karthik Babu Harichandra Babu, Praveen Ramoorthy" ) +# TODO: arobel: If Fabric -> Resources -> Per VRF Per VTEP Loopback IPv4 Auto-Provisioning +# is enabled, and dcnm_vrf merged-state playbook is run, the following error is seen. +# fatal: [172.22.150.244]: FAILED! => +# { +# "changed": false, +# "msg": +# { +# "DATA": { +# "Error": "Internal Server Error", +# "message": "per vrf level loopback is enabled and hence not allowed to clear the loopback ID or IP", +# "path": "/rest/top-down/fabrics/MSD/vrfs/attachments", +# "status": "500", +# "timestamp": "2024-11-28 01:35:15.164"}, +# "MESSAGE": "Internal Server Error", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.1.1.1:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/MSD/vrfs/attachments", +# "RETURN_CODE": 500 +# } +# } +# } + DOCUMENTATION = """ --- module: dcnm_vrf @@ -562,24 +583,27 @@ - vrf_name: ansible-vrf-r1 - vrf_name: ansible-vrf-r2 """ - -import json -import time -import copy import ast +import copy +import inspect +import json +import logging import re -from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import ( - get_fabric_inventory_details, - dcnm_send, - validate_list_of_dicts, - dcnm_get_ip_addr_info, - get_ip_sn_dict, - get_fabric_details, - get_ip_sn_fabric_dict, - dcnm_version_supported, - dcnm_get_url, -) +import time + from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import ( + dcnm_get_ip_addr_info, dcnm_get_url, dcnm_send, + get_fabric_inventory_details, get_ip_sn_dict, get_ip_sn_fabric_dict, + validate_list_of_dicts) + +from ..module_utils.common.controller_version import ControllerVersion +from ..module_utils.common.log_v2 import Log +from ..module_utils.common.response_handler import ResponseHandler +from ..module_utils.common.rest_send_v2 import RestSend +from ..module_utils.common.results import Results +from ..module_utils.common.sender_dcnm import Sender +from ..module_utils.fabric.fabric_details_v2 import FabricDetailsByName class DcnmVrf: @@ -603,6 +627,8 @@ class DcnmVrf: def __init__(self, module): + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") self.module = module self.params = module.params self.fabric = module.params["fabric"] @@ -634,11 +660,32 @@ def __init__(self, module): self.diff_delete = {} self.diff_input_format = [] self.query = [] - self.dcnm_version = dcnm_version_supported(self.module) + + self.sender = Sender() + self.sender.ansible_module = self.module + self.rest_send = RestSend(self.module.params) + self.rest_send.response_handler = ResponseHandler() + self.rest_send.sender = self.sender + + self.controller_version = ControllerVersion() + self.controller_version.rest_send = self.rest_send + self.controller_version.refresh() + self.dcnm_version = int(self.controller_version.version_major) + + self.fabric_details = FabricDetailsByName() + self.fabric_details.rest_send = self.rest_send + self.fabric_details.results = Results() + self.fabric_details.refresh() + self.fabric_details.filter = self.fabric + self.fabric_type = self.fabric_details.fabric_type + msg = f"ZZZ: {self.class_name}.__init__(): " + msg += f"fabric_type: {self.fabric_type}" + self.log.debug(msg) + self.inventory_data = get_fabric_inventory_details(self.module, self.fabric) self.ip_sn, self.hn_sn = get_ip_sn_dict(self.inventory_data) - self.fabric_data = get_fabric_details(self.module, self.fabric) - self.fabric_type = self.fabric_data.get("fabricType") + # self.fabric_data = get_fabric_details(self.module, self.fabric) + # self.fabric_type = self.fabric_data.get("fabricType") self.ip_fab, self.sn_fab = get_ip_sn_fabric_dict(self.inventory_data) if self.dcnm_version > 12: self.paths = self.dcnm_vrf_paths[12] @@ -650,6 +697,17 @@ def __init__(self, module): self.failed_to_rollback = False self.WAIT_TIME_FOR_DELETE_LOOP = 5 # in seconds + @staticmethod + def to_bool(key, dict_with_key): + value = dict_with_key.get(key) + try: + # TODO: Any string value e.g. "false" will return True here. + # We need to test for common bool-like strings e.g.: + #if value in ["false", False] return False + return bool(value) + except: + return value + def diff_for_attach_deploy(self, want_a, have_a, replace=False): attach_list = [] @@ -665,7 +723,9 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): for have in have_a: if want["serialNumber"] == have["serialNumber"]: # handle instanceValues first - want.update({"freeformConfig": have["freeformConfig"]}) # copy freeformConfig from have as module is not managing it + want.update( + {"freeformConfig": have["freeformConfig"]} + ) # copy freeformConfig from have as module is not managing it want_inst_values = {} have_inst_values = {} if ( @@ -675,15 +735,32 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want_inst_values = ast.literal_eval(want["instanceValues"]) have_inst_values = ast.literal_eval(have["instanceValues"]) - # update unsupported paramters using using have + # update unsupported parameters using have # Only need ipv4 or ipv6. Don't require both, but both can be supplied (as per the GUI) - want_inst_values.update({"loopbackId": have_inst_values["loopbackId"]}) + if "loopbackId" in have_inst_values: + want_inst_values.update( + {"loopbackId": have_inst_values["loopbackId"]} + ) if "loopbackIpAddress" in have_inst_values: - want_inst_values.update({"loopbackIpAddress": have_inst_values["loopbackIpAddress"]}) + want_inst_values.update( + { + "loopbackIpAddress": have_inst_values[ + "loopbackIpAddress" + ] + } + ) if "loopbackIpV6Address" in have_inst_values: - want_inst_values.update({"loopbackIpV6Address": have_inst_values["loopbackIpV6Address"]}) + want_inst_values.update( + { + "loopbackIpV6Address": have_inst_values[ + "loopbackIpV6Address" + ] + } + ) - want.update({"instanceValues": json.dumps(want_inst_values)}) + want.update( + {"instanceValues": json.dumps(want_inst_values)} + ) if ( want["extensionValues"] != "" and have["extensionValues"] != "" @@ -696,7 +773,10 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want_e = ast.literal_eval(want_ext_values["VRF_LITE_CONN"]) have_e = ast.literal_eval(have_ext_values["VRF_LITE_CONN"]) - if replace and (len(want_e["VRF_LITE_CONN"]) != len(have_e["VRF_LITE_CONN"])): + if replace and ( + len(want_e["VRF_LITE_CONN"]) + != len(have_e["VRF_LITE_CONN"]) + ): # In case of replace/override if the length of want and have lite attach of a switch # is not same then we have to push the want to NDFC. No further check is required for # this switch @@ -706,60 +786,49 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): for hlite in have_e["VRF_LITE_CONN"]: found = False interface_match = False - if wlite["IF_NAME"] == hlite["IF_NAME"]: - found = True - interface_match = True - if wlite["DOT1Q_ID"]: - if ( - wlite["DOT1Q_ID"] - != hlite["DOT1Q_ID"] - ): - found = False - break - - if wlite["IP_MASK"]: - if ( - wlite["IP_MASK"] - != hlite["IP_MASK"] - ): - found = False - break - - if wlite["NEIGHBOR_IP"]: - if ( - wlite["NEIGHBOR_IP"] - != hlite["NEIGHBOR_IP"] - ): - found = False - break - - if wlite["IPV6_MASK"]: - if ( - wlite["IPV6_MASK"] - != hlite["IPV6_MASK"] - ): - found = False - break - - if wlite["IPV6_NEIGHBOR"]: - if ( - wlite["IPV6_NEIGHBOR"] - != hlite["IPV6_NEIGHBOR"] - ): - found = False - break - - if wlite["PEER_VRF_NAME"]: - if ( - wlite["PEER_VRF_NAME"] - != hlite["PEER_VRF_NAME"] - ): - found = False - break - - if found: + if wlite["IF_NAME"] != hlite["IF_NAME"]: + continue + found = True + interface_match = True + if wlite["DOT1Q_ID"]: + if wlite["DOT1Q_ID"] != hlite["DOT1Q_ID"]: + found = False break + if wlite["IP_MASK"]: + if wlite["IP_MASK"] != hlite["IP_MASK"]: + found = False + break + + if wlite["NEIGHBOR_IP"]: + if wlite["NEIGHBOR_IP"] != hlite["NEIGHBOR_IP"]: + found = False + break + + if wlite["IPV6_MASK"]: + if wlite["IPV6_MASK"] != hlite["IPV6_MASK"]: + found = False + break + + if wlite["IPV6_NEIGHBOR"]: + if ( + wlite["IPV6_NEIGHBOR"] + != hlite["IPV6_NEIGHBOR"] + ): + found = False + break + + if wlite["PEER_VRF_NAME"]: + if ( + wlite["PEER_VRF_NAME"] + != hlite["PEER_VRF_NAME"] + ): + found = False + break + + if found: + break + if interface_match and not found: break @@ -781,21 +850,35 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): found = True else: found = True + want_is_deploy = self.to_bool("is_deploy", want) + have_is_deploy = self.to_bool("is_deploy", have) - if want.get("isAttached") is not None: - if bool(have["isAttached"]) is not bool( - want["isAttached"] - ): + want_is_attached = self.to_bool("isAttached", want) + have_is_attached = self.to_bool("isAttached", have) + + if have_is_attached != want_is_attached: + + if "isAttached" in want: del want["isAttached"] - want["deployment"] = True - attach_list.append(want) - if bool(want["is_deploy"]): - dep_vrf = True - continue - if ((bool(want["deployment"]) is not bool(have["deployment"])) or - (bool(want["is_deploy"]) is not bool(have["is_deploy"]))): - if bool(want["is_deploy"]): + want["deployment"] = True + attach_list.append(want) + if want_is_deploy is True: + dep_vrf = True + continue + + want_deployment = self.to_bool("deployment", want) + have_deployment = self.to_bool("deployment", have) + + if want_deployment is not None: + want_deployment = bool(want_deployment) + if have_deployment is not None: + have_deployment = bool(have_deployment) + + if (want_deployment != have_deployment) or ( + want_is_deploy != have_is_deploy + ): + if want_is_deploy is True: dep_vrf = True for k, v in want_inst_values.items(): @@ -809,12 +892,14 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): break if not found: - if bool(want["isAttached"]): - del want["isAttached"] - want["deployment"] = True - attach_list.append(want) - if bool(want["is_deploy"]): - dep_vrf = True + if "isAttached" in want: + if bool(want["isAttached"]): + del want["isAttached"] + want["deployment"] = True + attach_list.append(want) + if "is_deploy" in want: + if bool(want["is_deploy"]): + dep_vrf = True return attach_list, dep_vrf @@ -834,18 +919,15 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): serial = ser if not serial: - self.module.fail_json( - msg="Fabric: {0} does not have the switch: {1}".format( - self.fabric, attach["ip_address"] - ) - ) + msg = f"Fabric {self.fabric} does not contain switch " + msg += f"{attach['ip_address']}" + self.module.fail_json(msg=msg) role = self.inventory_data[attach["ip_address"]].get("switchRole") if role.lower() == "spine" or role.lower() == "super spine": - msg = "VRFs cannot be attached to switch {0} with role {1}".format( - attach["ip_address"], role - ) + msg = f"VRFs cannot be attached to switch {attach['ip_address']} " + msg += f"with role {role}" self.module.fail_json(msg=msg) ext_values = {} @@ -855,12 +937,12 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): ext_values["MULTISITE_CONN"] = json.dumps(ms_con) if attach["vrf_lite"]: - """Before apply the vrf_lite config, need double check if the swtich role is started wth Border""" + # Before applying the vrf_lite config, verify that the switch role + # begins with border r = re.search(r"\bborder\b", role.lower()) if not r: - msg = "VRF LITE cannot be attached to switch {0} with role {1}".format( - attach["ip_address"], role - ) + msg = f"VRF LITE cannot be attached to switch {attach['ip_address']} " + msg += f"with role {role}" self.module.fail_json(msg=msg) at_lite = attach["vrf_lite"] @@ -875,55 +957,58 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): or a_l["peer_vrf"] ): - """if vrf lite elements are provided by the user in the playbook fill the extension values""" - vrflite_con = {} - vrflite_con["VRF_LITE_CONN"] = [] - vrflite_con["VRF_LITE_CONN"].append({}) - + # If the playbook contains vrf lite elements add the + # extension values + nbr_dict = {} if a_l["interface"]: - vrflite_con["VRF_LITE_CONN"][0]["IF_NAME"] = a_l["interface"] + nbr_dict["IF_NAME"] = a_l["interface"] else: - vrflite_con["VRF_LITE_CONN"][0]["IF_NAME"] = "" + nbr_dict["IF_NAME"] = "" if a_l["dot1q"]: - vrflite_con["VRF_LITE_CONN"][0]["DOT1Q_ID"] = str(a_l["dot1q"]) + nbr_dict["DOT1Q_ID"] = str(a_l["dot1q"]) else: - vrflite_con["VRF_LITE_CONN"][0]["DOT1Q_ID"] = "" + nbr_dict["DOT1Q_ID"] = "" if a_l["ipv4_addr"]: - vrflite_con["VRF_LITE_CONN"][0]["IP_MASK"] = a_l["ipv4_addr"] + nbr_dict["IP_MASK"] = a_l["ipv4_addr"] else: - vrflite_con["VRF_LITE_CONN"][0]["IP_MASK"] = "" + nbr_dict["IP_MASK"] = "" if a_l["neighbor_ipv4"]: - vrflite_con["VRF_LITE_CONN"][0]["NEIGHBOR_IP"] = a_l[ - "neighbor_ipv4" - ] + nbr_dict["NEIGHBOR_IP"] = a_l["neighbor_ipv4"] else: - vrflite_con["VRF_LITE_CONN"][0]["NEIGHBOR_IP"] = "" + nbr_dict["NEIGHBOR_IP"] = "" if a_l["ipv6_addr"]: - vrflite_con["VRF_LITE_CONN"][0]["IPV6_MASK"] = a_l["ipv6_addr"] + nbr_dict["IPV6_MASK"] = a_l["ipv6_addr"] else: - vrflite_con["VRF_LITE_CONN"][0]["IPV6_MASK"] = "" + nbr_dict["IPV6_MASK"] = "" if a_l["neighbor_ipv6"]: - vrflite_con["VRF_LITE_CONN"][0]["IPV6_NEIGHBOR"] = a_l[ - "neighbor_ipv6" - ] + nbr_dict["IPV6_NEIGHBOR"] = a_l["neighbor_ipv6"] else: - vrflite_con["VRF_LITE_CONN"][0]["IPV6_NEIGHBOR"] = "" + nbr_dict["IPV6_NEIGHBOR"] = "" if a_l["peer_vrf"]: - vrflite_con["VRF_LITE_CONN"][0]["PEER_VRF_NAME"] = a_l["peer_vrf"] + nbr_dict["PEER_VRF_NAME"] = a_l["peer_vrf"] else: - vrflite_con["VRF_LITE_CONN"][0]["PEER_VRF_NAME"] = "" + nbr_dict["PEER_VRF_NAME"] = "" + + nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = "Ext_VRF_Lite_Jython" - vrflite_con["VRF_LITE_CONN"][0][ - "VRF_LITE_JYTHON_TEMPLATE" - ] = "Ext_VRF_Lite_Jython" - if (ext_values["VRF_LITE_CONN"]): - ext_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend(vrflite_con["VRF_LITE_CONN"]) + msg = f"{self.class_name}.update_attach_params: " + msg += f"nbr_dict: {json.dumps(nbr_dict, indent=4, sort_keys=True)}" + self.log.debug(msg) + + vrflite_con = {} + vrflite_con["VRF_LITE_CONN"] = [] + vrflite_con["VRF_LITE_CONN"].append(copy.deepcopy(nbr_dict)) + + if ext_values["VRF_LITE_CONN"]: + ext_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend( + vrflite_con["VRF_LITE_CONN"] + ) else: ext_values["VRF_LITE_CONN"] = vrflite_con @@ -945,7 +1030,8 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): attach.update({"extensionValues": json.dumps(ext_values).replace(" ", "")}) else: attach.update({"extensionValues": ""}) - # freeformConfig, loopbackId. loopbackIpAddress, and loopbackIpV6Address will be copied from have + # freeformConfig, loopbackId, loopbackIpAddress, and loopbackIpV6Address + # will be copied from have attach.update({"freeformConfig": ""}) inst_values = { "loopbackId": "", @@ -953,17 +1039,31 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): "loopbackIpV6Address": "", } if self.dcnm_version > 11: - inst_values.update({ - "switchRouteTargetImportEvpn": attach["import_evpn_rt"], - "switchRouteTargetExportEvpn": attach["export_evpn_rt"], - }) + inst_values.update( + { + "switchRouteTargetImportEvpn": attach["import_evpn_rt"], + "switchRouteTargetExportEvpn": attach["export_evpn_rt"], + } + ) attach.update({"instanceValues": json.dumps(inst_values).replace(" ", "")}) if "deploy" in attach: del attach["deploy"] - del attach["ip_address"] + if "ip_address" in attach: + del attach["ip_address"] return attach + @staticmethod + def want_and_have_vrf_template_configs_differ(want_template, have_template) -> bool: + if sorted(want_template.keys()) != sorted(have_template.keys()): + return True + for key in want_template.keys(): + want_value = str(want_template[key]).lower() + have_value = str(have_template[key]).lower() + if want_value != have_value: + return True + return False + def diff_for_create(self, want, have): conf_changed = False @@ -976,114 +1076,22 @@ def diff_for_create(self, want, have): json_to_dict_have = json.loads(have["vrfTemplateConfig"]) vlanId_want = str(json_to_dict_want.get("vrfVlanId", "")) - vlanId_have = json_to_dict_have.get("vrfVlanId", "") - vlanName_want = json_to_dict_want.get("vrfVlanName", "") - vlanName_have = json_to_dict_have.get("vrfVlanName", "") - vlanIntf_want = json_to_dict_want.get("vrfIntfDescription", "") - vlanIntf_have = json_to_dict_have.get("vrfIntfDescription", "") - vrfDesc_want = json_to_dict_want.get("vrfDescription", "") - vrfDesc_have = json_to_dict_have.get("vrfDescription", "") - vrfMtu_want = str(json_to_dict_want.get("mtu", "")) - vrfMtu_have = json_to_dict_have.get("mtu", "") - vrfTag_want = str(json_to_dict_want.get("tag", "")) - vrfTag_have = json_to_dict_have.get("tag", "") - redRmap_want = json_to_dict_want.get("vrfRouteMap", "") - redRmap_have = json_to_dict_have.get("vrfRouteMap", "") - maxBgp_want = str(json_to_dict_want.get("maxBgpPaths", "")) - maxBgp_have = json_to_dict_have.get("maxBgpPaths", "") - maxiBgp_want = str(json_to_dict_want.get("maxIbgpPaths", "")) - maxiBgp_have = json_to_dict_have.get("maxIbgpPaths", "") - ipv6ll_want = str(json_to_dict_want.get("ipv6LinkLocalFlag", "")).lower() - ipv6ll_have = json_to_dict_have.get("ipv6LinkLocalFlag", "") - trmen_want = str(json_to_dict_want.get("trmEnabled", "")).lower() - trmen_have = json_to_dict_have.get("trmEnabled", "") - norp_want = str(json_to_dict_want.get("isRPAbsent", "")).lower() - norp_have = json_to_dict_have.get("isRPAbsent", "") - rpext_want = str(json_to_dict_want.get("isRPExternal", "")).lower() - rpext_have = json_to_dict_have.get("isRPExternal", "") - rpadd_want = json_to_dict_want.get("rpAddress", "") - rpadd_have = json_to_dict_have.get("rpAddress", "") - rploid_want = str(json_to_dict_want.get("loopbackNumber", "")) - rploid_have = json_to_dict_have.get("loopbackNumber", "") - mcastadd_want = json_to_dict_want.get("L3VniMcastGroup", "") - mcastadd_have = json_to_dict_have.get("L3VniMcastGroup", "") - mcastgrp_want = json_to_dict_want.get("multicastGroup", "") - mcastgrp_have = json_to_dict_have.get("multicastGroup", "") - trmBgwms_want = str(json_to_dict_want.get("trmBGWMSiteEnabled", "")).lower() - trmBgwms_have = json_to_dict_have.get("trmBGWMSiteEnabled", "") - advhrt_want = str(json_to_dict_want.get("advertiseHostRouteFlag", "")).lower() - advhrt_have = json_to_dict_have.get("advertiseHostRouteFlag", "") - advdrt_want = str(json_to_dict_want.get("advertiseDefaultRouteFlag", "")).lower() - advdrt_have = json_to_dict_have.get("advertiseDefaultRouteFlag", "") - constd_want = str(json_to_dict_want.get("configureStaticDefaultRouteFlag", "")).lower() - constd_have = json_to_dict_have.get("configureStaticDefaultRouteFlag", "") - bgppass_want = json_to_dict_want.get("bgpPassword", "") - bgppass_have = json_to_dict_have.get("bgpPassword", "") - bgppasskey_want = str(json_to_dict_want.get("bgpPasswordKeyType", "")) - bgppasskey_have = json_to_dict_have.get("bgpPasswordKeyType", "") - nfen_want = str(json_to_dict_want.get("ENABLE_NETFLOW", "")).lower() - nfen_have = json_to_dict_have.get("ENABLE_NETFLOW", "") - nfmon_want = json_to_dict_want.get("NETFLOW_MONITOR", "") - nfmon_have = json_to_dict_have.get("NETFLOW_MONITOR", "") - disrtauto_want = str(json_to_dict_want.get("disableRtAuto", "")).lower() - disrtauto_have = json_to_dict_have.get("disableRtAuto", "") - rtvpnImp_want = json_to_dict_want.get("routeTargetImport", "") - rtvpnImp_have = json_to_dict_have.get("routeTargetImport", "") - rtvpnExp_want = json_to_dict_want.get("routeTargetExport", "") - rtvpnExp_have = json_to_dict_have.get("routeTargetExport", "") - rtevpnImp_want = json_to_dict_want.get("routeTargetImportEvpn", "") - rtevpnImp_have = json_to_dict_have.get("routeTargetImportEvpn", "") - rtevpnExp_want = json_to_dict_want.get("routeTargetExportEvpn", "") - rtevpnExp_have = json_to_dict_have.get("routeTargetExportEvpn", "") - rtmvpnImp_want = json_to_dict_want.get("routeTargetImportMvpn", "") - rtmvpnImp_have = json_to_dict_have.get("routeTargetImportMvpn", "") - rtmvpnExp_want = json_to_dict_want.get("routeTargetExportMvpn", "") - rtmvpnExp_have = json_to_dict_have.get("routeTargetExportMvpn", "") + templates_differ = self.want_and_have_vrf_template_configs_differ( + json_to_dict_want, json_to_dict_have + ) if vlanId_want != "0": if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: - self.module.fail_json( - msg="vrf_id for vrf:{0} cant be updated to a different value".format( - want["vrfName"] - ) - ) + msg = f"{self.class_name}.diff_for_create: " + msg += f"vrf_id for vrf {want['vrfName']} cannot be updated to " + msg += "a different value" + self.module.fail_json(msg) elif ( have["serviceVrfTemplate"] != want["serviceVrfTemplate"] or have["vrfTemplate"] != want["vrfTemplate"] or have["vrfExtensionTemplate"] != want["vrfExtensionTemplate"] - or vlanId_have != vlanId_want - or vlanName_have != vlanName_want - or vlanIntf_have != vlanIntf_want - or vrfDesc_have != vrfDesc_want - or vrfMtu_have != vrfMtu_want - or vrfTag_have != vrfTag_want - or redRmap_have != redRmap_want - or maxBgp_have != maxBgp_want - or maxiBgp_have != maxiBgp_want - or ipv6ll_have != ipv6ll_want - or trmen_have != trmen_want - or norp_have != norp_want - or rpext_have != rpext_want - or rpadd_have != rpadd_want - or rploid_have != rploid_want - or mcastadd_have != mcastadd_want - or mcastgrp_have != mcastgrp_want - or trmBgwms_have != trmBgwms_want - or advhrt_have != advhrt_want - or advdrt_have != advdrt_want - or constd_have != constd_want - or bgppass_have != bgppass_want - or bgppasskey_have != bgppasskey_want - or nfen_have != nfen_want - or nfmon_have != nfmon_want - or disrtauto_have != disrtauto_want - or rtvpnImp_have != rtvpnImp_want - or rtvpnExp_have != rtvpnExp_want - or rtevpnImp_have != rtevpnImp_want - or rtevpnExp_have != rtevpnExp_want - or rtmvpnImp_have != rtmvpnImp_want - or rtmvpnExp_have != rtmvpnExp_want + or templates_differ ): conf_changed = True @@ -1098,46 +1106,16 @@ def diff_for_create(self, want, have): else: if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: - self.module.fail_json( - msg="vrf_id for vrf:{0} cant be updated to a different value".format( - want["vrfName"] - ) - ) + msg = f"{self.class_name}.diff_for_create: " + msg += f"vrf_id for vrf {want['vrfName']} cannot be updated to " + msg += "a different value" + self.module.fail_json(msg=msg) + elif ( have["serviceVrfTemplate"] != want["serviceVrfTemplate"] or have["vrfTemplate"] != want["vrfTemplate"] or have["vrfExtensionTemplate"] != want["vrfExtensionTemplate"] - or vlanName_have != vlanName_want - or vlanIntf_have != vlanIntf_want - or vrfDesc_have != vrfDesc_want - or vrfMtu_have != vrfMtu_want - or vrfTag_have != vrfTag_want - or redRmap_have != redRmap_want - or maxBgp_have != maxBgp_want - or maxiBgp_have != maxiBgp_want - or ipv6ll_have != ipv6ll_want - or trmen_have != trmen_want - or norp_have != norp_want - or rpext_have != rpext_want - or rpadd_have != rpadd_want - or rploid_have != rploid_want - or mcastadd_have != mcastadd_want - or mcastgrp_have != mcastgrp_want - or trmBgwms_have != trmBgwms_want - or advhrt_have != advhrt_want - or advdrt_have != advdrt_want - or constd_have != constd_want - or bgppass_have != bgppass_want - or bgppasskey_have != bgppasskey_want - or nfen_have != nfen_want - or nfmon_have != nfmon_want - or disrtauto_have != disrtauto_want - or rtvpnImp_have != rtvpnImp_want - or rtvpnExp_have != rtvpnExp_want - or rtevpnImp_have != rtevpnImp_want - or rtevpnExp_have != rtevpnExp_want - or rtmvpnImp_have != rtmvpnImp_want - or rtmvpnExp_have != rtmvpnExp_want + or templates_differ ): conf_changed = True @@ -1275,9 +1253,15 @@ def get_have(self): "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup", ""), "multicastGroup": json_to_dict.get("multicastGroup", ""), "trmBGWMSiteEnabled": json_to_dict.get("trmBGWMSiteEnabled", False), - "advertiseHostRouteFlag": json_to_dict.get("advertiseHostRouteFlag", False), - "advertiseDefaultRouteFlag": json_to_dict.get("advertiseDefaultRouteFlag", True), - "configureStaticDefaultRouteFlag": json_to_dict.get("configureStaticDefaultRouteFlag", True), + "advertiseHostRouteFlag": json_to_dict.get( + "advertiseHostRouteFlag", False + ), + "advertiseDefaultRouteFlag": json_to_dict.get( + "advertiseDefaultRouteFlag", True + ), + "configureStaticDefaultRouteFlag": json_to_dict.get( + "configureStaticDefaultRouteFlag", True + ), "bgpPassword": json_to_dict.get("bgpPassword", ""), "bgpPasswordKeyType": json_to_dict.get("bgpPasswordKeyType", ""), } @@ -1287,12 +1271,24 @@ def get_have(self): t_conf.update(ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW", False)) t_conf.update(NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR", "")) t_conf.update(disableRtAuto=json_to_dict.get("disableRtAuto", False)) - t_conf.update(routeTargetImport=json_to_dict.get("routeTargetImport", "")) - t_conf.update(routeTargetExport=json_to_dict.get("routeTargetExport", "")) - t_conf.update(routeTargetImportEvpn=json_to_dict.get("routeTargetImportEvpn", "")) - t_conf.update(routeTargetExportEvpn=json_to_dict.get("routeTargetExportEvpn", "")) - t_conf.update(routeTargetImportMvpn=json_to_dict.get("routeTargetImportMvpn", "")) - t_conf.update(routeTargetExportMvpn=json_to_dict.get("routeTargetExportMvpn", "")) + t_conf.update( + routeTargetImport=json_to_dict.get("routeTargetImport", "") + ) + t_conf.update( + routeTargetExport=json_to_dict.get("routeTargetExport", "") + ) + t_conf.update( + routeTargetImportEvpn=json_to_dict.get("routeTargetImportEvpn", "") + ) + t_conf.update( + routeTargetExportEvpn=json_to_dict.get("routeTargetExportEvpn", "") + ) + t_conf.update( + routeTargetImportMvpn=json_to_dict.get("routeTargetImportMvpn", "") + ) + t_conf.update( + routeTargetExportMvpn=json_to_dict.get("routeTargetExportMvpn", "") + ) vrf.update({"vrfTemplateConfig": json.dumps(t_conf)}) del vrf["vrfStatus"] @@ -1393,22 +1389,24 @@ def get_have(self): vrflite_con["VRF_LITE_CONN"][0]["IPV6_MASK"] = ev[ "IPV6_MASK" ] - vrflite_con["VRF_LITE_CONN"][0][ - "IPV6_NEIGHBOR" - ] = ev["IPV6_NEIGHBOR"] + vrflite_con["VRF_LITE_CONN"][0]["IPV6_NEIGHBOR"] = ( + ev["IPV6_NEIGHBOR"] + ) vrflite_con["VRF_LITE_CONN"][0][ "AUTO_VRF_LITE_FLAG" ] = "false" - vrflite_con["VRF_LITE_CONN"][0][ - "PEER_VRF_NAME" - ] = ev["PEER_VRF_NAME"] + vrflite_con["VRF_LITE_CONN"][0]["PEER_VRF_NAME"] = ( + ev["PEER_VRF_NAME"] + ) vrflite_con["VRF_LITE_CONN"][0][ "VRF_LITE_JYTHON_TEMPLATE" ] = "Ext_VRF_Lite_Jython" - if (extension_values["VRF_LITE_CONN"]): - extension_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend(vrflite_con["VRF_LITE_CONN"]) + if extension_values["VRF_LITE_CONN"]: + extension_values["VRF_LITE_CONN"][ + "VRF_LITE_CONN" + ].extend(vrflite_con["VRF_LITE_CONN"]) else: extension_values["VRF_LITE_CONN"] = vrflite_con @@ -1418,12 +1416,8 @@ def get_have(self): ms_con = {} ms_con["MULTISITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = json.dumps( - ms_con - ) - e_values = json.dumps(extension_values).replace( - " ", "" - ) + extension_values["MULTISITE_CONN"] = json.dumps(ms_con) + e_values = json.dumps(extension_values).replace(" ", "") attach.update({"extensionValues": e_values}) @@ -1524,10 +1518,11 @@ def get_diff_delete(self): to_del = [] atch_h = have_a["lanAttachList"] for a_h in atch_h: - if a_h["isAttached"]: - del a_h["isAttached"] - a_h.update({"deployment": False}) - to_del.append(a_h) + if "isAttached" in a_h: + if a_h["isAttached"]: + del a_h["isAttached"] + a_h.update({"deployment": False}) + to_del.append(a_h) if to_del: have_a.update({"lanAttachList": to_del}) diff_detach.append(have_a) @@ -1540,10 +1535,11 @@ def get_diff_delete(self): to_del = [] atch_h = have_a["lanAttachList"] for a_h in atch_h: - if a_h["isAttached"]: - del a_h["isAttached"] - a_h.update({"deployment": False}) - to_del.append(a_h) + if "isAttached" in a_h: + if a_h["isAttached"]: + del a_h["isAttached"] + a_h.update({"deployment": False}) + to_del.append(a_h) if to_del: have_a.update({"lanAttachList": to_del}) diff_detach.append(have_a) @@ -1583,10 +1579,11 @@ def get_diff_override(self): if not found: atch_h = have_a["lanAttachList"] for a_h in atch_h: - if a_h["isAttached"]: - del a_h["isAttached"] - a_h.update({"deployment": False}) - to_del.append(a_h) + if "isAttached" in a_h: + if a_h["isAttached"]: + del a_h["isAttached"] + a_h.update({"deployment": False}) + to_del.append(a_h) if to_del: have_a.update({"lanAttachList": to_del}) @@ -1624,8 +1621,9 @@ def get_diff_replace(self): atch_w = want_a.get("lanAttachList") for a_h in atch_h: - if not a_h["isAttached"]: - continue + if "isAttached" in a_h: + if not a_h["isAttached"]: + continue a_match = False if atch_w: @@ -1635,7 +1633,8 @@ def get_diff_replace(self): a_match = True break if not a_match: - del a_h["isAttached"] + if "isAttached" in a_h: + del a_h["isAttached"] a_h.update({"deployment": False}) r_vrf_list.append(a_h) break @@ -1652,11 +1651,12 @@ def get_diff_replace(self): if found: atch_h = have_a["lanAttachList"] for a_h in atch_h: - if not bool(a_h["isAttached"]): - continue - del a_h["isAttached"] - a_h.update({"deployment": False}) - r_vrf_list.append(a_h) + if "isAttached" in a_h: + if not bool(a_h["isAttached"]): + continue + del a_h["isAttached"] + a_h.update({"deployment": False}) + r_vrf_list.append(a_h) if r_vrf_list: in_diff = False @@ -1771,39 +1771,85 @@ def get_diff_merge(self, replace=False): "vrfName": want_c["vrfName"], "vrfVlanId": json_to_dict.get("vrfVlanId"), "vrfVlanName": json_to_dict.get("vrfVlanName"), - "vrfIntfDescription": json_to_dict.get("vrfIntfDescription"), + "vrfIntfDescription": json_to_dict.get( + "vrfIntfDescription" + ), "vrfDescription": json_to_dict.get("vrfDescription"), "mtu": json_to_dict.get("mtu"), "tag": json_to_dict.get("tag"), "vrfRouteMap": json_to_dict.get("vrfRouteMap"), "maxBgpPaths": json_to_dict.get("maxBgpPaths"), "maxIbgpPaths": json_to_dict.get("maxIbgpPaths"), - "ipv6LinkLocalFlag": json_to_dict.get("ipv6LinkLocalFlag"), + "ipv6LinkLocalFlag": json_to_dict.get( + "ipv6LinkLocalFlag" + ), "trmEnabled": json_to_dict.get("trmEnabled"), "isRPExternal": json_to_dict.get("isRPExternal"), "rpAddress": json_to_dict.get("rpAddress"), "loopbackNumber": json_to_dict.get("loopbackNumber"), "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup"), "multicastGroup": json_to_dict.get("multicastGroup"), - "trmBGWMSiteEnabled": json_to_dict.get("trmBGWMSiteEnabled"), - "advertiseHostRouteFlag": json_to_dict.get("advertiseHostRouteFlag"), - "advertiseDefaultRouteFlag": json_to_dict.get("advertiseDefaultRouteFlag"), - "configureStaticDefaultRouteFlag": json_to_dict.get("configureStaticDefaultRouteFlag"), + "trmBGWMSiteEnabled": json_to_dict.get( + "trmBGWMSiteEnabled" + ), + "advertiseHostRouteFlag": json_to_dict.get( + "advertiseHostRouteFlag" + ), + "advertiseDefaultRouteFlag": json_to_dict.get( + "advertiseDefaultRouteFlag" + ), + "configureStaticDefaultRouteFlag": json_to_dict.get( + "configureStaticDefaultRouteFlag" + ), "bgpPassword": json_to_dict.get("bgpPassword"), - "bgpPasswordKeyType": json_to_dict.get("bgpPasswordKeyType"), + "bgpPasswordKeyType": json_to_dict.get( + "bgpPasswordKeyType" + ), } if self.dcnm_version > 11: - template_conf.update(isRPAbsent=json_to_dict.get("isRPAbsent")) - template_conf.update(ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW")) - template_conf.update(NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR")) - template_conf.update(disableRtAuto=json_to_dict.get("disableRtAuto")) - template_conf.update(routeTargetImport=json_to_dict.get("routeTargetImport")) - template_conf.update(routeTargetExport=json_to_dict.get("routeTargetExport")) - template_conf.update(routeTargetImportEvpn=json_to_dict.get("routeTargetImportEvpn")) - template_conf.update(routeTargetExportEvpn=json_to_dict.get("routeTargetExportEvpn")) - template_conf.update(routeTargetImportMvpn=json_to_dict.get("routeTargetImportMvpn")) - template_conf.update(routeTargetExportMvpn=json_to_dict.get("routeTargetExportMvpn")) + template_conf.update( + isRPAbsent=json_to_dict.get("isRPAbsent") + ) + template_conf.update( + ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW") + ) + template_conf.update( + NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR") + ) + template_conf.update( + disableRtAuto=json_to_dict.get("disableRtAuto") + ) + template_conf.update( + routeTargetImport=json_to_dict.get( + "routeTargetImport" + ) + ) + template_conf.update( + routeTargetExport=json_to_dict.get( + "routeTargetExport" + ) + ) + template_conf.update( + routeTargetImportEvpn=json_to_dict.get( + "routeTargetImportEvpn" + ) + ) + template_conf.update( + routeTargetExportEvpn=json_to_dict.get( + "routeTargetExportEvpn" + ) + ) + template_conf.update( + routeTargetImportMvpn=json_to_dict.get( + "routeTargetImportMvpn" + ) + ) + template_conf.update( + routeTargetExportMvpn=json_to_dict.get( + "routeTargetExportMvpn" + ) + ) want_c.update( {"vrfTemplateConfig": json.dumps(template_conf)} @@ -1867,8 +1913,9 @@ def get_diff_merge(self, replace=False): del base["lanAttachList"] base.update({"lanAttachList": atch_list}) diff_attach.append(base) - if bool(attach["is_deploy"]): - dep_vrf = want_a["vrfName"] + if attach.get("is_deploy") is not None: + if bool(attach.get("is_deploy")): + dep_vrf = want_a["vrfName"] for atch in atch_list: atch["deployment"] = True @@ -1887,6 +1934,11 @@ def get_diff_merge(self, replace=False): def format_diff(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + msg += f"ENTERED" + self.log.debug(msg) + diff = [] diff_create = copy.deepcopy(self.diff_create) @@ -1927,37 +1979,79 @@ def format_diff(self): json_to_dict = json.loads(found_c["vrfTemplateConfig"]) found_c.update({"vrf_vlan_name": json_to_dict.get("vrfVlanName", "")}) - found_c.update({"vrf_intf_desc": json_to_dict.get("vrfIntfDescription", "")}) + found_c.update( + {"vrf_intf_desc": json_to_dict.get("vrfIntfDescription", "")} + ) found_c.update({"vrf_description": json_to_dict.get("vrfDescription", "")}) found_c.update({"vrf_int_mtu": json_to_dict.get("mtu", "")}) found_c.update({"loopback_route_tag": json_to_dict.get("tag", "")}) found_c.update({"redist_direct_rmap": json_to_dict.get("vrfRouteMap", "")}) found_c.update({"max_bgp_paths": json_to_dict.get("maxBgpPaths", "")}) found_c.update({"max_ibgp_paths": json_to_dict.get("maxIbgpPaths", "")}) - found_c.update({"ipv6_linklocal_enable": json_to_dict.get("ipv6LinkLocalFlag", True)}) + found_c.update( + {"ipv6_linklocal_enable": json_to_dict.get("ipv6LinkLocalFlag", True)} + ) found_c.update({"trm_enable": json_to_dict.get("trmEnabled", False)}) found_c.update({"rp_external": json_to_dict.get("isRPExternal", False)}) found_c.update({"rp_address": json_to_dict.get("rpAddress", "")}) found_c.update({"rp_loopback_id": json_to_dict.get("loopbackNumber", "")}) - found_c.update({"underlay_mcast_ip": json_to_dict.get("L3VniMcastGroup", "")}) - found_c.update({"overlay_mcast_group": json_to_dict.get("multicastGroup", "")}) - found_c.update({"trm_bgw_msite": json_to_dict.get("trmBGWMSiteEnabled", False)}) - found_c.update({"adv_host_routes": json_to_dict.get("advertiseHostRouteFlag", False)}) - found_c.update({"adv_default_routes": json_to_dict.get("advertiseDefaultRouteFlag", True)}) - found_c.update({"static_default_route": json_to_dict.get("configureStaticDefaultRouteFlag", True)}) + found_c.update( + {"underlay_mcast_ip": json_to_dict.get("L3VniMcastGroup", "")} + ) + found_c.update( + {"overlay_mcast_group": json_to_dict.get("multicastGroup", "")} + ) + found_c.update( + {"trm_bgw_msite": json_to_dict.get("trmBGWMSiteEnabled", False)} + ) + found_c.update( + {"adv_host_routes": json_to_dict.get("advertiseHostRouteFlag", False)} + ) + found_c.update( + { + "adv_default_routes": json_to_dict.get( + "advertiseDefaultRouteFlag", True + ) + } + ) + found_c.update( + { + "static_default_route": json_to_dict.get( + "configureStaticDefaultRouteFlag", True + ) + } + ) found_c.update({"bgp_password": json_to_dict.get("bgpPassword", "")}) - found_c.update({"bgp_passwd_encrypt": json_to_dict.get("bgpPasswordKeyType", "")}) + found_c.update( + {"bgp_passwd_encrypt": json_to_dict.get("bgpPasswordKeyType", "")} + ) if self.dcnm_version > 11: found_c.update({"no_rp": json_to_dict.get("isRPAbsent", False)}) - found_c.update({"netflow_enable": json_to_dict.get("ENABLE_NETFLOW", True)}) + found_c.update( + {"netflow_enable": json_to_dict.get("ENABLE_NETFLOW", True)} + ) found_c.update({"nf_monitor": json_to_dict.get("NETFLOW_MONITOR", "")}) - found_c.update({"disable_rt_auto": json_to_dict.get("disableRtAuto", False)}) - found_c.update({"import_vpn_rt": json_to_dict.get("routeTargetImport", "")}) - found_c.update({"export_vpn_rt": json_to_dict.get("routeTargetExport", "")}) - found_c.update({"import_evpn_rt": json_to_dict.get("routeTargetImportEvpn", "")}) - found_c.update({"export_evpn_rt": json_to_dict.get("routeTargetExportEvpn", "")}) - found_c.update({"import_mvpn_rt": json_to_dict.get("routeTargetImportMvpn", "")}) - found_c.update({"export_mvpn_rt": json_to_dict.get("routeTargetExportMvpn", "")}) + found_c.update( + {"disable_rt_auto": json_to_dict.get("disableRtAuto", False)} + ) + found_c.update( + {"import_vpn_rt": json_to_dict.get("routeTargetImport", "")} + ) + found_c.update( + {"export_vpn_rt": json_to_dict.get("routeTargetExport", "")} + ) + found_c.update( + {"import_evpn_rt": json_to_dict.get("routeTargetImportEvpn", "")} + ) + found_c.update( + {"export_evpn_rt": json_to_dict.get("routeTargetExportEvpn", "")} + ) + found_c.update( + {"import_mvpn_rt": json_to_dict.get("routeTargetImportMvpn", "")} + ) + found_c.update( + {"export_mvpn_rt": json_to_dict.get("routeTargetExportMvpn", "")} + ) del found_c["fabric"] del found_c["vrfName"] @@ -2019,6 +2113,11 @@ def format_diff(self): self.diff_input_format = diff + msg = f"{self.class_name}.{method_name}: " + msg += "self.diff_input_format: " + msg += f"{json.dumps(self.diff_input_format, indent=4, sort_keys=True)}" + self.log.debug(msg) + def get_diff_query(self): method = "GET" @@ -2148,6 +2247,11 @@ def get_diff_query(self): self.query = query def push_to_remote(self, is_rollback=False): + method_name = inspect.stack()[0][3] + + msg = f"{self.class_name}.{method_name}: " + msg += "ENTERED" + self.log.debug(msg) path = self.paths["GET_VRF"].format(self.fabric) @@ -2273,9 +2377,15 @@ def push_to_remote(self, is_rollback=False): "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup"), "multicastGroup": json_to_dict.get("multicastGroup"), "trmBGWMSiteEnabled": json_to_dict.get("trmBGWMSiteEnabled"), - "advertiseHostRouteFlag": json_to_dict.get("advertiseHostRouteFlag"), - "advertiseDefaultRouteFlag": json_to_dict.get("advertiseDefaultRouteFlag"), - "configureStaticDefaultRouteFlag": json_to_dict.get("configureStaticDefaultRouteFlag"), + "advertiseHostRouteFlag": json_to_dict.get( + "advertiseHostRouteFlag" + ), + "advertiseDefaultRouteFlag": json_to_dict.get( + "advertiseDefaultRouteFlag" + ), + "configureStaticDefaultRouteFlag": json_to_dict.get( + "configureStaticDefaultRouteFlag" + ), "bgpPassword": json_to_dict.get("bgpPassword"), "bgpPasswordKeyType": json_to_dict.get("bgpPasswordKeyType"), } @@ -2285,12 +2395,24 @@ def push_to_remote(self, is_rollback=False): t_conf.update(ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW")) t_conf.update(NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR")) t_conf.update(disableRtAuto=json_to_dict.get("disableRtAuto")) - t_conf.update(routeTargetImport=json_to_dict.get("routeTargetImport")) - t_conf.update(routeTargetExport=json_to_dict.get("routeTargetExport")) - t_conf.update(routeTargetImportEvpn=json_to_dict.get("routeTargetImportEvpn")) - t_conf.update(routeTargetExportEvpn=json_to_dict.get("routeTargetExportEvpn")) - t_conf.update(routeTargetImportMvpn=json_to_dict.get("routeTargetImportMvpn")) - t_conf.update(routeTargetExportMvpn=json_to_dict.get("routeTargetExportMvpn")) + t_conf.update( + routeTargetImport=json_to_dict.get("routeTargetImport") + ) + t_conf.update( + routeTargetExport=json_to_dict.get("routeTargetExport") + ) + t_conf.update( + routeTargetImportEvpn=json_to_dict.get("routeTargetImportEvpn") + ) + t_conf.update( + routeTargetExportEvpn=json_to_dict.get("routeTargetExportEvpn") + ) + t_conf.update( + routeTargetImportMvpn=json_to_dict.get("routeTargetImportMvpn") + ) + t_conf.update( + routeTargetExportMvpn=json_to_dict.get("routeTargetExportMvpn") + ) vrf.update({"vrfTemplateConfig": json.dumps(t_conf)}) @@ -2341,95 +2463,85 @@ def push_to_remote(self, is_rollback=False): extension_values["MULTISITE_CONN"] = [] for ext_l in lite: - if str(ext_l.get("extensionType")) == "VRF_LITE": - ext_values = ext_l["extensionValues"] - ext_values = ast.literal_eval(ext_values) - for ad_l in v_a.get("vrf_lite"): - if ad_l["interface"] == ext_values["IF_NAME"]: - vrflite_con = {} - vrflite_con["VRF_LITE_CONN"] = [] - vrflite_con["VRF_LITE_CONN"].append({}) - vrflite_con["VRF_LITE_CONN"][0][ - "IF_NAME" - ] = ad_l["interface"] - - if ad_l["dot1q"]: - vrflite_con["VRF_LITE_CONN"][0][ - "DOT1Q_ID" - ] = str(ad_l["dot1q"]) - else: - vrflite_con["VRF_LITE_CONN"][0][ - "DOT1Q_ID" - ] = str(ext_values["DOT1Q_ID"]) - - if ad_l["ipv4_addr"]: - vrflite_con["VRF_LITE_CONN"][0][ - "IP_MASK" - ] = ad_l["ipv4_addr"] - else: - vrflite_con["VRF_LITE_CONN"][0][ - "IP_MASK" - ] = ext_values["IP_MASK"] - - if ad_l["neighbor_ipv4"]: - vrflite_con["VRF_LITE_CONN"][0][ - "NEIGHBOR_IP" - ] = ad_l["neighbor_ipv4"] - else: - vrflite_con["VRF_LITE_CONN"][0][ - "NEIGHBOR_IP" - ] = ext_values["NEIGHBOR_IP"] - - vrflite_con["VRF_LITE_CONN"][0][ - "NEIGHBOR_ASN" - ] = ext_values["NEIGHBOR_ASN"] - - if ad_l["ipv6_addr"]: - vrflite_con["VRF_LITE_CONN"][0][ - "IPV6_MASK" - ] = ad_l["ipv6_addr"] - else: - vrflite_con["VRF_LITE_CONN"][0][ - "IPV6_MASK" - ] = ext_values["IPV6_MASK"] - - if ad_l["neighbor_ipv6"]: - vrflite_con["VRF_LITE_CONN"][0][ - "IPV6_NEIGHBOR" - ] = ad_l["neighbor_ipv6"] - else: - vrflite_con["VRF_LITE_CONN"][0][ - "IPV6_NEIGHBOR" - ] = ext_values["IPV6_NEIGHBOR"] - - vrflite_con["VRF_LITE_CONN"][0][ - "AUTO_VRF_LITE_FLAG" - ] = ext_values["AUTO_VRF_LITE_FLAG"] - - if ad_l["peer_vrf"]: - vrflite_con["VRF_LITE_CONN"][0][ - "PEER_VRF_NAME" - ] = ad_l["peer_vrf"] - else: - vrflite_con["VRF_LITE_CONN"][0][ - "PEER_VRF_NAME" - ] = ext_values["PEER_VRF_NAME"] - - vrflite_con["VRF_LITE_CONN"][0][ - "VRF_LITE_JYTHON_TEMPLATE" - ] = "Ext_VRF_Lite_Jython" - if (extension_values["VRF_LITE_CONN"]): - extension_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend(vrflite_con["VRF_LITE_CONN"]) - else: - extension_values["VRF_LITE_CONN"] = vrflite_con - - ms_con = {} - ms_con["MULTISITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = json.dumps( - ms_con + if str(ext_l.get("extensionType")) != "VRF_LITE": + continue + ext_values = ext_l["extensionValues"] + ext_values = ast.literal_eval(ext_values) + for ad_l in v_a.get("vrf_lite"): + if ad_l["interface"] == ext_values["IF_NAME"]: + nbr_dict = {} + nbr_dict["IF_NAME"] = ad_l["interface"] + + if ad_l["dot1q"]: + nbr_dict["DOT1Q_ID"] = str(ad_l["dot1q"]) + else: + nbr_dict["DOT1Q_ID"] = str( + ext_values["DOT1Q_ID"] ) - del ad_l + if ad_l["ipv4_addr"]: + nbr_dict["IP_MASK"] = ad_l["ipv4_addr"] + else: + nbr_dict["IP_MASK"] = ext_values["IP_MASK"] + + if ad_l["neighbor_ipv4"]: + nbr_dict["NEIGHBOR_IP"] = ad_l["neighbor_ipv4"] + else: + nbr_dict["NEIGHBOR_IP"] = ext_values[ + "NEIGHBOR_IP" + ] + + nbr_dict["NEIGHBOR_ASN"] = ext_values[ + "NEIGHBOR_ASN" + ] + + if ad_l["ipv6_addr"]: + nbr_dict["IPV6_MASK"] = ad_l["ipv6_addr"] + else: + nbr_dict["IPV6_MASK"] = ext_values["IPV6_MASK"] + + if ad_l["neighbor_ipv6"]: + nbr_dict["IPV6_NEIGHBOR"] = ad_l[ + "neighbor_ipv6" + ] + else: + nbr_dict["IPV6_NEIGHBOR"] = ext_values[ + "IPV6_NEIGHBOR" + ] + + nbr_dict["AUTO_VRF_LITE_FLAG"] = ext_values[ + "AUTO_VRF_LITE_FLAG" + ] + + if ad_l["peer_vrf"]: + nbr_dict["PEER_VRF_NAME"] = ad_l["peer_vrf"] + else: + nbr_dict["PEER_VRF_NAME"] = ext_values[ + "PEER_VRF_NAME" + ] + + nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = ( + "Ext_VRF_Lite_Jython" + ) + vrflite_con = {} + vrflite_con["VRF_LITE_CONN"] = [] + vrflite_con["VRF_LITE_CONN"].append( + copy.deepcopy(nbr_dict) + ) + if extension_values["VRF_LITE_CONN"]: + extension_values["VRF_LITE_CONN"][ + "VRF_LITE_CONN" + ].extend(vrflite_con["VRF_LITE_CONN"]) + else: + extension_values["VRF_LITE_CONN"] = vrflite_con + + ms_con = {} + ms_con["MULTISITE_CONN"] = [] + extension_values["MULTISITE_CONN"] = json.dumps( + ms_con + ) + + del ad_l if ext_values is None: for ip, ser in self.ip_sn.items(): @@ -2487,6 +2599,7 @@ def push_to_remote(self, is_rollback=False): self.failure(resp) def wait_for_vrf_del_ready(self): + method_name = inspect.stack()[0][3] method = "GET" if self.diff_delete: @@ -2498,6 +2611,10 @@ def wait_for_vrf_del_ready(self): state = True if resp.get("DATA") is not None: attach_list = resp["DATA"][0]["lanAttachList"] + msg = f"{self.class_name}.{method_name}: " + msg += f"attach_list: {json.dumps(attach_list, indent=4)}" + self.log.debug(msg) + for atch in attach_list: if ( atch["lanAttachState"] == "OUT-OF-SYNC" @@ -2505,10 +2622,30 @@ def wait_for_vrf_del_ready(self): ): self.diff_delete.update({vrf: "OUT-OF-SYNC"}) break + if ( + atch["lanAttachState"] == "DEPLOYED" + and atch["isLanAttached"] == True + ): + vrf_name = atch.get("vrfName", "unknown") + fabric_name = atch.get("fabricName", "unknown") + switch_ip = atch.get("ipAddress", "unknown") + switch_name = atch.get("switchName", "unknown") + vlan_id = atch.get("vlanId", "unknown") + msg = f"{self.class_name}.{method_name}: " + msg += f"Network attachments associated with vrf {vrf_name} " + msg += f"must be removed (e.g. using the dcnm_network module) " + msg += f"prior to deleting the vrf. " + msg += f"Details: fabric_name: {fabric_name}, " + msg += f"vrf_name: {vrf_name}. " + msg += f"Network attachments found on " + msg += f"switch_ip: {switch_ip}, " + msg += f"switch_name: {switch_name}, " + msg += f"vlan_id: {vlan_id}" + self.module.fail_json(msg=msg) if atch["lanAttachState"] != "NA": + time.sleep(self.WAIT_TIME_FOR_DELETE_LOOP) self.diff_delete.update({vrf: "DEPLOYED"}) state = False - time.sleep(self.WAIT_TIME_FOR_DELETE_LOOP) break self.diff_delete.update({vrf: "NA"}) @@ -2536,9 +2673,15 @@ def validate_input(self): vrf_vlan_name=dict(type="str", default=""), vrf_intf_desc=dict(type="str", default=""), vrf_description=dict(type="str", default=""), - vrf_int_mtu=dict(type="int", range_min=68, range_max=9216, default=9216), - loopback_route_tag=dict(type="int", default=12345, range_max=4294967295), - redist_direct_rmap=dict(type="str", default="FABRIC-RMAP-REDIST-SUBNET"), + vrf_int_mtu=dict( + type="int", range_min=68, range_max=9216, default=9216 + ), + loopback_route_tag=dict( + type="int", default=12345, range_max=4294967295 + ), + redist_direct_rmap=dict( + type="str", default="FABRIC-RMAP-REDIST-SUBNET" + ), max_bgp_paths=dict(type="int", range_min=1, range_max=64, default=1), max_ibgp_paths=dict(type="int", range_min=1, range_max=64, default=2), ipv6_linklocal_enable=dict(type="bool", default=True), @@ -2655,9 +2798,15 @@ def validate_input(self): vrf_vlan_name=dict(type="str", default=""), vrf_intf_desc=dict(type="str", default=""), vrf_description=dict(type="str", default=""), - vrf_int_mtu=dict(type="int", range_min=68, range_max=9216, default=9216), - loopback_route_tag=dict(type="int", default=12345, range_max=4294967295), - redist_direct_rmap=dict(type="str", default="FABRIC-RMAP-REDIST-SUBNET"), + vrf_int_mtu=dict( + type="int", range_min=68, range_max=9216, default=9216 + ), + loopback_route_tag=dict( + type="int", default=12345, range_max=4294967295 + ), + redist_direct_rmap=dict( + type="str", default="FABRIC-RMAP-REDIST-SUBNET" + ), max_bgp_paths=dict(type="int", range_min=1, range_max=64, default=1), max_ibgp_paths=dict(type="int", range_min=1, range_max=64, default=2), ipv6_linklocal_enable=dict(type="bool", default=True), @@ -2817,14 +2966,19 @@ def main(): module = AnsibleModule(argument_spec=element_spec, supports_check_mode=True) + # Logging setup + try: + log = Log() + log.commit() + except (TypeError, ValueError) as error: + module.fail_json(str(error)) + dcnm_vrf = DcnmVrf(module) if not dcnm_vrf.ip_sn: - module.fail_json( - msg="Fabric {0} missing on DCNM or does not have any switches".format( - dcnm_vrf.fabric - ) - ) + msg = f"Fabric {dcnm_vrf.fabric} missing on the controller or " + msg += "does not have any switches" + module.fail_json(msg=msg) dcnm_vrf.validate_input() From 0b1771526e6bf2394b0f8b2e739d006d0ff148f6 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 3 Dec 2024 07:53:53 -1000 Subject: [PATCH 02/49] dcnm_vrf: to_bool() fix to return correct value, or call fail_json() The initial implementation would return True for e.g. "false" since bool(non-null-string) is always True. 1. Modify to explicitly compare against known boolean-like strings i.e. "false", "False", "true", and "True". 2. Add the caller to the error message for better debugging ability in the future. --- plugins/modules/dcnm_vrf.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index df7284ea5..6585595d3 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -697,16 +697,35 @@ def __init__(self, module): self.failed_to_rollback = False self.WAIT_TIME_FOR_DELETE_LOOP = 5 # in seconds - @staticmethod - def to_bool(key, dict_with_key): + def to_bool(self, key, dict_with_key): + """ + # Summary + + Given a dictionary and key, access dictionary[key] and + try to convert the value therein to a boolean. + + - If the value is a boolean, return a like boolean. + - If the value is a boolean-like string (e.g. "false" + "True", etc), return the value converted to boolean. + + ## Raises + + - Call fail_json() if the value is not convertable to boolean. + """ value = dict_with_key.get(key) - try: - # TODO: Any string value e.g. "false" will return True here. - # We need to test for common bool-like strings e.g.: - #if value in ["false", False] return False - return bool(value) - except: - return value + if value in ["false", "False", False]: + return False + if value in ["true", "True", True]: + return True + + method_name = inspect.stack()[0][3] + caller = inspect.stack()[1][3] + + msg = f"{self.class_name}.{method_name}: " + msg += f"caller: {caller}: " + msg = f"{str(value)} with type {type(value)} " + msg += "is not convertable to boolean" + self.module.fail_json(msg=msg) def diff_for_attach_deploy(self, want_a, have_a, replace=False): From 6e32f42bedb919ea355cb59fb8650494db9e8072 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 3 Dec 2024 04:40:28 -1000 Subject: [PATCH 03/49] dcnm_image_policy: fix for issue #347 (#348) * Fix for issue 347 Manually tested this to verify. Still need to update integration and unit tests. * dcnm_image_policy: Update integration test Update integration test for overridden state. 1. playbooks/roles/dcnm_image_policy/dcnm_tests.yaml - Add vars - install_package_1 - uninstall_package_1 2. test/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml - Add packages.install and packages.uninstall configuration - Verify that merged state adds these packages to the controller config - Verify that overridden state removes packages.install and packages.uninstall - Verify that overridden state metadata.action is "replace" instead of "update" --- .../roles/dcnm_image_policy/dcnm_tests.yaml | 2 + plugins/modules/dcnm_image_policy.py | 8 ++-- .../tests/dcnm_image_policy_overridden.yaml | 42 +++++++++++++------ 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/playbooks/roles/dcnm_image_policy/dcnm_tests.yaml b/playbooks/roles/dcnm_image_policy/dcnm_tests.yaml index 87d4937d3..206b68e2c 100644 --- a/playbooks/roles/dcnm_image_policy/dcnm_tests.yaml +++ b/playbooks/roles/dcnm_image_policy/dcnm_tests.yaml @@ -25,6 +25,8 @@ leaf2: 192.168.1.5 leaf3: 192.168.1.6 leaf4: 192.168.1.7 + install_package_1: cfg_cmp-0.3.1.0-1.x86_64.rpm + uninstall_package_1: mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000 image_policy_1: "KR5M" image_policy_2: "NR3F" epld_image_1: n9000-epld.10.2.5.M.img diff --git a/plugins/modules/dcnm_image_policy.py b/plugins/modules/dcnm_image_policy.py index 6aab7ebbc..e56364c71 100644 --- a/plugins/modules/dcnm_image_policy.py +++ b/plugins/modules/dcnm_image_policy.py @@ -592,7 +592,7 @@ def __init__(self, params): raise ValueError(msg) from error self.delete = ImagePolicyDelete() - self.merged = Merged(params) + self.replaced = Replaced(params) msg = f"ENTERED {self.class_name}().{method_name}: " msg += f"state: {self.state}, " @@ -627,10 +627,10 @@ def commit(self) -> None: self._delete_policies_not_in_want() # pylint: disable=attribute-defined-outside-init - self.merged.rest_send = self.rest_send + self.replaced.rest_send = self.rest_send # pylint: enable=attribute-defined-outside-init - self.merged.results = self.results - self.merged.commit() + self.replaced.results = self.results + self.replaced.commit() def _delete_policies_not_in_want(self) -> None: """ diff --git a/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml index 0c77fd3e4..02838c3f1 100644 --- a/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml +++ b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml @@ -12,11 +12,13 @@ # # SETUP # 1. The following images must already be uploaded to the controller -# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# See vars: section in cisco/dcnm/playbooks/dcnm_image_policy/dcnm_tests.yaml # - nxos_image_1 # - nxos_image_2 # - epld_image_1 # - epld_image_2 +# - install_package_1 +# - uninstall_package_1 # 2. No need for fabric or switches # 3. Delete image policies under test, if they exist # - image_policy_1 @@ -43,6 +45,8 @@ # - nxos_image_2 # - epld_image_1 # - epld_image_2 +# - install_package_1 +# - uninstall_package_1 # 2. No need for fabric or switches # # Example vars for dcnm_image_policy integration tests @@ -71,6 +75,8 @@ # nxos_image_2: n9000-dk9.10.3.1.F.bin # nxos_release_1: 10.2.5_nxos64-cs_64bit # nxos_release_2: 10.3.1_nxos64-cs_64bit +# install_package_1: cfg_cmp-0.3.1.0-1.x86_64.rpm +# uninstall_package_1: mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000 # ################################################################################ # SETUP @@ -99,10 +105,12 @@ # "agnostic": false, # "epldImgName": "n9000-epld.10.2.5.M.img", # "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "cfg_cmp-0.3.1.0-1.x86_64.rpm", # "platform": "N9K", # "policyDescr": "KR5M", # "policyName": "KR5M", # "policyType": "PLATFORM", +# "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000", # "sequence_number": 1 # }, # { @@ -162,7 +170,8 @@ # } # ] # } -# }################################################################################ +# } +################################################################################ - name: OVERRIDDEN - TEST - Create two image policies using merged state cisco.dcnm.dcnm_image_policy: @@ -172,6 +181,11 @@ agnostic: false description: "{{ image_policy_1 }}" epld_image: "{{ epld_image_1 }}" + packages: + install: + - "{{ install_package_1 }}" + uninstall: + - "{{ uninstall_package_1 }}" platform: N9K release: "{{ nxos_release_1 }}" type: PLATFORM @@ -190,6 +204,8 @@ - result.changed == true - result.failed == false - (result.diff | length) == 2 + - result.diff[0].packageName == install_package_1 + - result.diff[0].rpmimages == uninstall_package_1 - result.diff[0].policyName == image_policy_1 - result.diff[1].policyName == image_policy_2 - result.diff[0].policyDescr == image_policy_1 @@ -249,18 +265,14 @@ # { # "agnostic": false, # "epldImgName": "n9000-epld.10.2.5.M.img", -# "fabricPolicyName": null, -# "imagePresent": "Present", # "nxosVersion": "10.2.5_nxos64-cs_64bit", # "packageName": "", # "platform": "N9K", # "policyDescr": "KR5M overridden", # "policyName": "KR5M", # "policyType": "PLATFORM", -# "role": null, -# "rpmimages": null, -# "sequence_number": 2, -# "unInstall": false +# "rpmimages": "", +# "sequence_number": 2 # } # ], # "failed": false, @@ -272,7 +284,7 @@ # "state": "overridden" # }, # { -# "action": "update", +# "action": "replace", # "check_mode": false, # "sequence_number": 2, # "state": "overridden" @@ -334,6 +346,8 @@ - image_policy_2 in result.diff[0].policyNames - result.diff[0].sequence_number == 1 - result.diff[1].agnostic == false + - result.diff[1].packageName == "" + - result.diff[1].rpmimages == "" - result.diff[1].policyName == image_policy_1 - result.diff[1].policyDescr == image_policy_1 + " overridden" - result.diff[1].epldImgName == epld_image_1 @@ -343,7 +357,7 @@ - result.diff[1].sequence_number == 2 - (result.metadata | length) == 2 - result.metadata[0].action == "delete" - - result.metadata[1].action == "update" + - result.metadata[1].action == "replace" - result.metadata[0].state == "overridden" - result.metadata[1].state == "overridden" - result.metadata[0].check_mode == False @@ -371,7 +385,7 @@ # OVERRIDDEN - TEST - query image policies and verify results ################################################################################ # Expected result -# ok: [172.22.150.244] => { +# ok: [dcnm] => { # "result": { # "changed": false, # "diff": [ @@ -390,7 +404,7 @@ # "policyType": "PLATFORM", # "ref_count": 0, # "role": null, -# "rpmimages": null, +# "rpmimages": "", # "sequence_number": 1, # "unInstall": false # } @@ -423,7 +437,7 @@ # "policyType": "PLATFORM", # "ref_count": 0, # "role": null, -# "rpmimages": null, +# "rpmimages": "", # "unInstall": false # } # ], @@ -465,6 +479,8 @@ - result.failed == false - (result.diff | length) == 1 - result.diff[0].agnostic == false + - result.diff[0].packageName == "" + - result.diff[0].rpmimages == "" - result.diff[0].policyName == image_policy_1 - result.diff[0].policyDescr == image_policy_1 + " overridden" - result.diff[0].epldImgName == epld_image_1 From 244929b6fb6bae09373c1b33fa785ad29507dd51 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 3 Dec 2024 04:40:46 -1000 Subject: [PATCH 04/49] dcnm_fabric: hardening (#349) Two bits of vulnerable code found when porting to ndfc-python. 1. plugins/modules/dcnm_fabric.py Accessing dictionary key directly can lead to a KeyError exception. 2. plugins/module_utils/fabric/replaced.py If user omits the DEPLOY parameter from their playbook (ndfc-python) the DEPLOY key would be None, and not get popped from the payload. This would cause NDFC to complain about an invalid key in the payload. We need to unconditionally pop DEPLOY here, if it's present. Hence, we've removed the value check (if DEPLOY is not None). --- plugins/module_utils/fabric/replaced.py | 2 +- plugins/modules/dcnm_fabric.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/fabric/replaced.py b/plugins/module_utils/fabric/replaced.py index 1d42b761f..096d47931 100644 --- a/plugins/module_utils/fabric/replaced.py +++ b/plugins/module_utils/fabric/replaced.py @@ -468,7 +468,7 @@ def _send_payloads(self): for payload in self._payloads_to_commit: commit_payload = copy.deepcopy(payload) - if commit_payload.get("DEPLOY", None) is not None: + if "DEPLOY" in commit_payload: commit_payload.pop("DEPLOY") try: self._send_payload(commit_payload) diff --git a/plugins/modules/dcnm_fabric.py b/plugins/modules/dcnm_fabric.py index e3a1ba67b..4129da30a 100644 --- a/plugins/modules/dcnm_fabric.py +++ b/plugins/modules/dcnm_fabric.py @@ -3032,7 +3032,7 @@ def get_need(self): fabric_name = want.get("FABRIC_NAME", None) fabric_type = want.get("FABRIC_TYPE", None) - if self.features[fabric_type] is False: + if self.features.get("fabric_type") is False: msg = f"{self.class_name}.{method_name}: " msg += f"Features required for fabric {fabric_name} " msg += f"of type {fabric_type} are not running on the " @@ -3361,7 +3361,7 @@ def get_need(self): self.need_create.append(want) continue - if self.features[fabric_type] is False: + if self.features.get("fabric_type") is False: msg = f"{self.class_name}.{method_name}: " msg += f"Features required for fabric {fabric_name} " msg += f"of type {fabric_type} are not running on the " From 5f6718737f243e2fd78e2cfdd328f1a3525244d2 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 3 Dec 2024 17:56:01 -1000 Subject: [PATCH 05/49] dcnm_vrf: remove bool() casts, more... 1. Removed all instances where values were cast to bool. These potentially could result in bad results e.g. bool("false") returns True. 2. Renamed and fixed want_and_have_vrf_template_configs_differ(). Renamed to dict_values_differ() Fix was to add a skip_keys parameter so that we can skip vrfVlanId in one of the elif()s 3. Added some debugging statements. --- plugins/modules/dcnm_vrf.py | 331 +++++++++++++++++++++++++++--------- 1 file changed, 249 insertions(+), 82 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 6585595d3..bb5aa56e1 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -591,19 +591,26 @@ import re import time -from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import ( - dcnm_get_ip_addr_info, dcnm_get_url, dcnm_send, - get_fabric_inventory_details, get_ip_sn_dict, get_ip_sn_fabric_dict, - validate_list_of_dicts) + get_fabric_inventory_details, + dcnm_send, + validate_list_of_dicts, + dcnm_get_ip_addr_info, + get_ip_sn_dict, + get_fabric_details, + get_ip_sn_fabric_dict, + dcnm_version_supported, + dcnm_get_url, +) +from ansible.module_utils.basic import AnsibleModule -from ..module_utils.common.controller_version import ControllerVersion +# from ..module_utils.common.controller_version import ControllerVersion from ..module_utils.common.log_v2 import Log -from ..module_utils.common.response_handler import ResponseHandler -from ..module_utils.common.rest_send_v2 import RestSend -from ..module_utils.common.results import Results -from ..module_utils.common.sender_dcnm import Sender -from ..module_utils.fabric.fabric_details_v2 import FabricDetailsByName +# from ..module_utils.common.response_handler import ResponseHandler +# from ..module_utils.common.rest_send_v2 import RestSend +# from ..module_utils.common.results import Results +# from ..module_utils.common.sender_dcnm import Sender +# from ..module_utils.fabric.fabric_details_v2 import FabricDetailsByName class DcnmVrf: @@ -627,8 +634,6 @@ class DcnmVrf: def __init__(self, module): - self.class_name = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") self.module = module self.params = module.params self.fabric = module.params["fabric"] @@ -660,32 +665,11 @@ def __init__(self, module): self.diff_delete = {} self.diff_input_format = [] self.query = [] - - self.sender = Sender() - self.sender.ansible_module = self.module - self.rest_send = RestSend(self.module.params) - self.rest_send.response_handler = ResponseHandler() - self.rest_send.sender = self.sender - - self.controller_version = ControllerVersion() - self.controller_version.rest_send = self.rest_send - self.controller_version.refresh() - self.dcnm_version = int(self.controller_version.version_major) - - self.fabric_details = FabricDetailsByName() - self.fabric_details.rest_send = self.rest_send - self.fabric_details.results = Results() - self.fabric_details.refresh() - self.fabric_details.filter = self.fabric - self.fabric_type = self.fabric_details.fabric_type - msg = f"ZZZ: {self.class_name}.__init__(): " - msg += f"fabric_type: {self.fabric_type}" - self.log.debug(msg) - + self.dcnm_version = dcnm_version_supported(self.module) self.inventory_data = get_fabric_inventory_details(self.module, self.fabric) self.ip_sn, self.hn_sn = get_ip_sn_dict(self.inventory_data) - # self.fabric_data = get_fabric_details(self.module, self.fabric) - # self.fabric_type = self.fabric_data.get("fabricType") + self.fabric_data = get_fabric_details(self.module, self.fabric) + self.fabric_type = self.fabric_data.get("fabricType") self.ip_fab, self.sn_fab = get_ip_sn_fabric_dict(self.inventory_data) if self.dcnm_version > 12: self.paths = self.dcnm_vrf_paths[12] @@ -697,6 +681,12 @@ def __init__(self, module): self.failed_to_rollback = False self.WAIT_TIME_FOR_DELETE_LOOP = 5 # in seconds + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = f"{self.class_name}.__init__(): DONE" + self.log.debug(msg) + def to_bool(self, key, dict_with_key): """ # Summary @@ -728,6 +718,9 @@ def to_bool(self, key, dict_with_key): self.module.fail_json(msg=msg) def diff_for_attach_deploy(self, want_a, have_a, replace=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) attach_list = [] @@ -889,11 +882,6 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want_deployment = self.to_bool("deployment", want) have_deployment = self.to_bool("deployment", have) - if want_deployment is not None: - want_deployment = bool(want_deployment) - if have_deployment is not None: - have_deployment = bool(have_deployment) - if (want_deployment != have_deployment) or ( want_is_deploy != have_is_deploy ): @@ -912,17 +900,34 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if not found: if "isAttached" in want: - if bool(want["isAttached"]): + msg = f"{self.class_name}.{method_name}: " + msg += f"TYPE: want[isAttached]: {type(want['isAttached'])}" + self.log.debug(msg) + try: + if want["isAttached"]: del want["isAttached"] want["deployment"] = True attach_list.append(want) - if "is_deploy" in want: - if bool(want["is_deploy"]): - dep_vrf = True + if want["is_deploy"]: + dep_vrf = True + except: + msg = f"{self.class_name}.{method_name}: " + msg += "Unexpected values for " + msg += f"isAttached ({want.get("isAttached")}) " + msg += f"and/or is_deploy ({want.get("is_deploy")})" + self.log.debug(msg) + self.module.fail_json(msg=msg) + msg = f"{self.class_name}.{method_name}: " + msg += f"dep_vrf: {dep_vrf}, " + msg += f"attach_list: {json.dumps(attach_list, indent=4, sort_keys=True)}" + self.log.debug(msg) return attach_list, dep_vrf def update_attach_params(self, attach, vrf_name, deploy, vlanId): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) vrf_ext = False @@ -1070,20 +1075,41 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): if "ip_address" in attach: del attach["ip_address"] + msg = f"{self.class_name}.{method_name}: " + msg += f"attach: {json.dumps(attach, indent=4, sort_keys=True)}" + self.log.debug(msg) return attach - @staticmethod - def want_and_have_vrf_template_configs_differ(want_template, have_template) -> bool: - if sorted(want_template.keys()) != sorted(have_template.keys()): - return True + def dict_values_differ(self, want_template, have_template, skip_keys=[]) -> bool: + """ + # Summary + + Given a two dictionaries and, optionally, a list of keys to skip: + + - Return True if the values for any (non-skipped) keys differs. + - Return False otherwise + """ + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) + for key in want_template.keys(): + if key in skip_keys: + continue want_value = str(want_template[key]).lower() have_value = str(have_template[key]).lower() if want_value != have_value: + msg = f"{self.class_name}.{method_name}: " + msg += f"DIFFERS: key {key} want_value {want_value} != have_value {have_value}. " + msg += f"returning True" + self.log.debug(msg) return True return False def diff_for_create(self, want, have): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) conf_changed = False if not have: @@ -1095,60 +1121,70 @@ def diff_for_create(self, want, have): json_to_dict_have = json.loads(have["vrfTemplateConfig"]) vlanId_want = str(json_to_dict_want.get("vrfVlanId", "")) - templates_differ = self.want_and_have_vrf_template_configs_differ( - json_to_dict_want, json_to_dict_have - ) if vlanId_want != "0": + templates_differ = self.dict_values_differ( + json_to_dict_want, json_to_dict_have + ) + + msg = f"{self.class_name}.{method_name}: " + msg += f"templates_differ: {templates_differ}, vlanId_want: {vlanId_want}" + self.log.debug(msg) + if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: msg = f"{self.class_name}.diff_for_create: " msg += f"vrf_id for vrf {want['vrfName']} cannot be updated to " msg += "a different value" self.module.fail_json(msg) - elif ( - have["serviceVrfTemplate"] != want["serviceVrfTemplate"] - or have["vrfTemplate"] != want["vrfTemplate"] - or have["vrfExtensionTemplate"] != want["vrfExtensionTemplate"] - or templates_differ - ): + elif (templates_differ): conf_changed = True if want["vrfId"] is None: # The vrf updates with missing vrfId will have to use existing # vrfId from the instance of the same vrf on DCNM. want["vrfId"] = have["vrfId"] create = want + else: pass else: + # skip vrfVlanId when comparing + templates_differ = self.dict_values_differ( + json_to_dict_want, json_to_dict_have, + ["vrfVlanId"] + ) + if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: msg = f"{self.class_name}.diff_for_create: " msg += f"vrf_id for vrf {want['vrfName']} cannot be updated to " msg += "a different value" self.module.fail_json(msg=msg) - elif ( - have["serviceVrfTemplate"] != want["serviceVrfTemplate"] - or have["vrfTemplate"] != want["vrfTemplate"] - or have["vrfExtensionTemplate"] != want["vrfExtensionTemplate"] - or templates_differ - ): - + elif (templates_differ): conf_changed = True if want["vrfId"] is None: # The vrf updates with missing vrfId will have to use existing # vrfId from the instance of the same vrf on DCNM. want["vrfId"] = have["vrfId"] create = want + else: pass + msg = f"{self.class_name}.{method_name}: " + msg += f"returning conf_changed: {conf_changed}, " + msg += f"create: {create}" + self.log.debug(msg) + return create, conf_changed def update_create_params(self, vrf, vlanId=""): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) if not vrf: return vrf @@ -1214,6 +1250,9 @@ def update_create_params(self, vrf, vlanId=""): return vrf_upd def get_have(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) have_create = [] have_deploy = {} @@ -1323,8 +1362,11 @@ def get_have(self): for attach in attach_list: attach_state = False if attach["lanAttachState"] == "NA" else True deploy = attach["isLanAttached"] + msg = f"{self.class_name}.{method_name}: " + msg += f"TYPE: deploy: {type(deploy)}" + self.log.debug(msg) deployed = False - if bool(deploy) and ( + if deploy and ( attach["lanAttachState"] == "OUT-OF-SYNC" or attach["lanAttachState"] == "PENDING" ): @@ -1332,7 +1374,11 @@ def get_have(self): else: deployed = True - if bool(deployed): + msg = f"{self.class_name}.{method_name}: " + msg += f"TYPE: deployed: {type(deployed)}" + self.log.debug(msg) + + if deployed: dep_vrf = attach["vrfName"] sn = attach["switchSerialNo"] @@ -1455,7 +1501,24 @@ def get_have(self): self.have_attach = have_attach self.have_deploy = have_deploy + msg = f"{self.class_name}.{method_name}: " + msg += f"self.have_create: {json.dumps(self.have_create, indent=4)}" + self.log.debug(msg) + + # json.dumps() here breaks unit tests since self.have_attach is + # a MagicMock and not JSON serializable. + msg = f"{self.class_name}.{method_name}: " + msg += f"self.have_attach: {self.have_attach}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.have_deploy: {json.dumps(self.have_deploy, indent=4)}" + self.log.debug(msg) + def get_want(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) want_create = [] want_attach = [] @@ -1466,6 +1529,10 @@ def get_want(self): if not self.config: return + msg = f"{self.class_name}.{method_name}: " + msg += f"self.config {json.dumps(self.config, indent=4)}" + self.log.debug(msg) + for vrf in self.validated: vrf_attach = {} vrfs = [] @@ -1500,7 +1567,22 @@ def get_want(self): self.want_attach = want_attach self.want_deploy = want_deploy + msg = f"{self.class_name}.{method_name}: " + msg += f"self.want_create: {json.dumps(self.want_create, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.want_attach: {json.dumps(self.want_attach, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.want_deploy: {json.dumps(self.want_deploy, indent=4)}" + self.log.debug(msg) + def get_diff_delete(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) diff_detach = [] diff_undeploy = {} @@ -1572,7 +1654,22 @@ def get_diff_delete(self): self.diff_undeploy = diff_undeploy self.diff_delete = diff_delete + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_detach: {json.dumps(self.diff_detach, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_undeploy: {json.dumps(self.diff_undeploy, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_delete: {json.dumps(self.diff_delete, indent=4)}" + self.log.debug(msg) + def get_diff_override(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) all_vrfs = "" diff_delete = {} @@ -1621,7 +1718,34 @@ def get_diff_override(self): self.diff_undeploy = diff_undeploy self.diff_delete = diff_delete + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_create: {json.dumps(self.diff_create, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_attach: {json.dumps(self.diff_attach, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_deploy: {json.dumps(self.diff_deploy, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_detach: {json.dumps(self.diff_detach, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_undeploy: {json.dumps(self.diff_undeploy, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_delete: {json.dumps(self.diff_delete, indent=4)}" + self.log.debug(msg) + def get_diff_replace(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) all_vrfs = "" @@ -1671,7 +1795,10 @@ def get_diff_replace(self): atch_h = have_a["lanAttachList"] for a_h in atch_h: if "isAttached" in a_h: - if not bool(a_h["isAttached"]): + msg = f"{self.class_name}.{method_name}: " + msg += f"TYPE: a_h[isAttached]: {type(a_h['isAttached'])}" + self.log.debug(msg) + if not a_h["isAttached"]: continue del a_h["isAttached"] a_h.update({"deployment": False}) @@ -1709,7 +1836,22 @@ def get_diff_replace(self): self.diff_attach = diff_attach self.diff_deploy = diff_deploy + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_create: {json.dumps(self.diff_create, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_attach: {json.dumps(self.diff_attach, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_deploy: {json.dumps(self.diff_deploy, indent=4)}" + self.log.debug(msg) + def get_diff_merge(self, replace=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) # Special cases: # 1. Auto generate vrfId if its not mentioned by user: @@ -1932,9 +2074,8 @@ def get_diff_merge(self, replace=False): del base["lanAttachList"] base.update({"lanAttachList": atch_list}) diff_attach.append(base) - if attach.get("is_deploy") is not None: - if bool(attach.get("is_deploy")): - dep_vrf = want_a["vrfName"] + if attach["is_deploy"]: + dep_vrf = want_a["vrfName"] for atch in atch_list: atch["deployment"] = True @@ -1951,11 +2092,29 @@ def get_diff_merge(self, replace=False): self.diff_deploy = diff_deploy self.diff_create_quick = diff_create_quick - def format_diff(self): + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_create: {json.dumps(self.diff_create, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_create_update: {json.dumps(self.diff_create_update, indent=4)}" + self.log.debug(msg) - method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " - msg += f"ENTERED" + msg += f"self.diff_attach: {json.dumps(self.diff_attach, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_deploy: {json.dumps(self.diff_deploy, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_create_quick: {json.dumps(self.diff_create_quick, indent=4)}" + self.log.debug(msg) + + def format_diff(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) diff = [] @@ -2138,6 +2297,9 @@ def format_diff(self): self.log.debug(msg) def get_diff_query(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) method = "GET" path = self.paths["GET_VRF"].format(self.fabric) @@ -2148,9 +2310,9 @@ def get_diff_query(self): vrf_objects.get("ERROR") == "Not Found" and vrf_objects.get("RETURN_CODE") == 404 ): - self.module.fail_json( - msg="Fabric {0} not present on DCNM".format(self.fabric) - ) + msg = f"{self.class_name}.{method_name}: " + msg += f"Fabric {self.fabric} does not exist on the controller" + self.module.fail_json(msg=msg) return if missing_fabric or not_ok: @@ -2267,9 +2429,7 @@ def get_diff_query(self): def push_to_remote(self, is_rollback=False): method_name = inspect.stack()[0][3] - - msg = f"{self.class_name}.{method_name}: " - msg += "ENTERED" + msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) path = self.paths["GET_VRF"].format(self.fabric) @@ -2619,6 +2779,8 @@ def push_to_remote(self, is_rollback=False): def wait_for_vrf_del_ready(self): method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) method = "GET" if self.diff_delete: @@ -2672,6 +2834,9 @@ def wait_for_vrf_del_ready(self): def validate_input(self): """Parse the playbook values, validate to param specs.""" + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) state = self.params["state"] @@ -2896,6 +3061,9 @@ def validate_input(self): self.module.fail_json(msg=msg) def handle_response(self, res, op): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) fail = False changed = True @@ -2927,8 +3095,7 @@ def handle_response(self, res, op): return fail, changed def failure(self, resp): - - # Donot Rollback for Multi-site fabrics + # Do not Rollback for Multi-site fabrics if self.fabric_type == "MFD": self.failed_to_rollback = True self.module.fail_json(msg=resp) From 950884039f6348998e08321f240424515797d462 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 4 Dec 2024 11:25:31 -1000 Subject: [PATCH 06/49] dcnm_vrf: More refactoring and simplifying --- plugins/modules/dcnm_vrf.py | 349 ++++++++---------- .../unit/modules/dcnm/fixtures/dcnm_vrf.json | 12 +- tests/unit/modules/dcnm/test_dcnm_vrf.py | 14 +- 3 files changed, 176 insertions(+), 199 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index bb5aa56e1..55a2183f4 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -591,21 +591,22 @@ import re import time +from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import ( - get_fabric_inventory_details, - dcnm_send, - validate_list_of_dicts, dcnm_get_ip_addr_info, - get_ip_sn_dict, + dcnm_get_url, + dcnm_send, + dcnm_version_supported, get_fabric_details, + get_fabric_inventory_details, + get_ip_sn_dict, get_ip_sn_fabric_dict, - dcnm_version_supported, - dcnm_get_url, + validate_list_of_dicts, ) -from ansible.module_utils.basic import AnsibleModule # from ..module_utils.common.controller_version import ControllerVersion from ..module_utils.common.log_v2 import Log + # from ..module_utils.common.response_handler import ResponseHandler # from ..module_utils.common.rest_send_v2 import RestSend # from ..module_utils.common.results import Results @@ -727,7 +728,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if not want_a: return attach_list - dep_vrf = False + deploy_vrf = False for want in want_a: found = False interface_match = False @@ -876,7 +877,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want["deployment"] = True attach_list.append(want) if want_is_deploy is True: - dep_vrf = True + deploy_vrf = True continue want_deployment = self.to_bool("deployment", want) @@ -886,11 +887,10 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want_is_deploy != have_is_deploy ): if want_is_deploy is True: - dep_vrf = True + deploy_vrf = True - for k, v in want_inst_values.items(): - if v != have_inst_values.get(k, ""): - found = False + if self.dict_values_differ(want_inst_values, have_inst_values): + found = False if found: break @@ -899,30 +899,26 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): break if not found: - if "isAttached" in want: - msg = f"{self.class_name}.{method_name}: " - msg += f"TYPE: want[isAttached]: {type(want['isAttached'])}" - self.log.debug(msg) try: if want["isAttached"]: del want["isAttached"] want["deployment"] = True attach_list.append(want) if want["is_deploy"]: - dep_vrf = True + deploy_vrf = True except: msg = f"{self.class_name}.{method_name}: " msg += "Unexpected values for " - msg += f"isAttached ({want.get("isAttached")}) " - msg += f"and/or is_deploy ({want.get("is_deploy")})" + msg += f"isAttached ({want.get('isAttached')}) " + msg += f"and/or is_deploy ({want.get('is_deploy')})" self.log.debug(msg) self.module.fail_json(msg=msg) msg = f"{self.class_name}.{method_name}: " - msg += f"dep_vrf: {dep_vrf}, " + msg += f"deploy_vrf: {deploy_vrf}, " msg += f"attach_list: {json.dumps(attach_list, indent=4, sort_keys=True)}" self.log.debug(msg) - return attach_list, dep_vrf + return attach_list, deploy_vrf def update_attach_params(self, attach, vrf_name, deploy, vlanId): method_name = inspect.stack()[0][3] @@ -963,59 +959,57 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): if attach["vrf_lite"]: # Before applying the vrf_lite config, verify that the switch role # begins with border - r = re.search(r"\bborder\b", role.lower()) - if not r: + if not re.search(r"\bborder\b", role.lower()): msg = f"VRF LITE cannot be attached to switch {attach['ip_address']} " msg += f"with role {role}" self.module.fail_json(msg=msg) - at_lite = attach["vrf_lite"] - for a_l in at_lite: + for item in attach["vrf_lite"]: if ( - a_l["interface"] - or a_l["dot1q"] - or a_l["ipv4_addr"] - or a_l["neighbor_ipv4"] - or a_l["ipv6_addr"] - or a_l["neighbor_ipv6"] - or a_l["peer_vrf"] + item["interface"] + or item["dot1q"] + or item["ipv4_addr"] + or item["neighbor_ipv4"] + or item["ipv6_addr"] + or item["neighbor_ipv6"] + or item["peer_vrf"] ): # If the playbook contains vrf lite elements add the # extension values nbr_dict = {} - if a_l["interface"]: - nbr_dict["IF_NAME"] = a_l["interface"] + if item["interface"]: + nbr_dict["IF_NAME"] = item["interface"] else: nbr_dict["IF_NAME"] = "" - if a_l["dot1q"]: - nbr_dict["DOT1Q_ID"] = str(a_l["dot1q"]) + if item["dot1q"]: + nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) else: nbr_dict["DOT1Q_ID"] = "" - if a_l["ipv4_addr"]: - nbr_dict["IP_MASK"] = a_l["ipv4_addr"] + if item["ipv4_addr"]: + nbr_dict["IP_MASK"] = item["ipv4_addr"] else: nbr_dict["IP_MASK"] = "" - if a_l["neighbor_ipv4"]: - nbr_dict["NEIGHBOR_IP"] = a_l["neighbor_ipv4"] + if item["neighbor_ipv4"]: + nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] else: nbr_dict["NEIGHBOR_IP"] = "" - if a_l["ipv6_addr"]: - nbr_dict["IPV6_MASK"] = a_l["ipv6_addr"] + if item["ipv6_addr"]: + nbr_dict["IPV6_MASK"] = item["ipv6_addr"] else: nbr_dict["IPV6_MASK"] = "" - if a_l["neighbor_ipv6"]: - nbr_dict["IPV6_NEIGHBOR"] = a_l["neighbor_ipv6"] + if item["neighbor_ipv6"]: + nbr_dict["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] else: nbr_dict["IPV6_NEIGHBOR"] = "" - if a_l["peer_vrf"]: - nbr_dict["PEER_VRF_NAME"] = a_l["peer_vrf"] + if item["peer_vrf"]: + nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] else: nbr_dict["PEER_VRF_NAME"] = "" @@ -1138,7 +1132,7 @@ def diff_for_create(self, want, have): msg += "a different value" self.module.fail_json(msg) - elif (templates_differ): + elif templates_differ: conf_changed = True if want["vrfId"] is None: # The vrf updates with missing vrfId will have to use existing @@ -1153,8 +1147,7 @@ def diff_for_create(self, want, have): # skip vrfVlanId when comparing templates_differ = self.dict_values_differ( - json_to_dict_want, json_to_dict_have, - ["vrfVlanId"] + json_to_dict_want, json_to_dict_have, skip_keys=["vrfVlanId"] ) if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: @@ -1163,7 +1156,7 @@ def diff_for_create(self, want, have): msg += "a different value" self.module.fail_json(msg=msg) - elif (templates_differ): + elif templates_differ: conf_changed = True if want["vrfId"] is None: # The vrf updates with missing vrfId will have to use existing @@ -1178,7 +1171,7 @@ def diff_for_create(self, want, have): msg += f"returning conf_changed: {conf_changed}, " msg += f"create: {create}" self.log.debug(msg) - + return create, conf_changed def update_create_params(self, vrf, vlanId=""): @@ -1249,16 +1242,12 @@ def update_create_params(self, vrf, vlanId=""): return vrf_upd - def get_have(self): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) - - have_create = [] - have_deploy = {} - - curr_vrfs = "" + def get_vrf_objects(self) -> dict: + """ + # Summary + Retrieve all VRF objects from the controller + """ method = "GET" path = self.paths["GET_VRF"].format(self.fabric) @@ -1267,11 +1256,48 @@ def get_have(self): missing_fabric, not_ok = self.handle_response(vrf_objects, "query_dcnm") if missing_fabric or not_ok: - msg1 = "Fabric {0} not present on DCNM".format(self.fabric) - msg2 = "Unable to find vrfs under fabric: {0}".format(self.fabric) - + msg1 = f"Fabric {self.fabric} not present on the controller" + msg2 = f"Unable to find vrfs under fabric: {self.fabric}" self.module.fail_json(msg=msg1 if missing_fabric else msg2) + return copy.deepcopy(vrf_objects) + + def get_vrf_lite_objects(self, attach) -> dict: + """ + # Summary + + Retrieve the IP/Interface that is connected to the switch with serial_number + + attach must contain at least the following keys: + + - fabric: The fabric to search + - serialNumber: The serial_number of the switch + - vrfName: The vrf to search + """ + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) + + method = "GET" + path = self.paths["GET_VRF_SWITCH"].format( + attach["fabric"], attach["vrfName"], attach["serialNumber"] + ) + lite_objects = dcnm_send(self.module, method, path) + + return copy.deepcopy(lite_objects) + + def get_have(self): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) + + have_create = [] + have_deploy = {} + + curr_vrfs = "" + + vrf_objects = self.get_vrf_objects() + if not vrf_objects.get("DATA"): return @@ -1358,13 +1384,10 @@ def get_have(self): if not vrf_attach.get("lanAttachList"): continue attach_list = vrf_attach["lanAttachList"] - dep_vrf = "" + deploy_vrf = "" for attach in attach_list: attach_state = False if attach["lanAttachState"] == "NA" else True deploy = attach["isLanAttached"] - msg = f"{self.class_name}.{method_name}: " - msg += f"TYPE: deploy: {type(deploy)}" - self.log.debug(msg) deployed = False if deploy and ( attach["lanAttachState"] == "OUT-OF-SYNC" @@ -1374,21 +1397,17 @@ def get_have(self): else: deployed = True - msg = f"{self.class_name}.{method_name}: " - msg += f"TYPE: deployed: {type(deployed)}" - self.log.debug(msg) - if deployed: - dep_vrf = attach["vrfName"] + deploy_vrf = attach["vrfName"] sn = attach["switchSerialNo"] vlan = attach["vlanId"] inst_values = attach.get("instanceValues", None) - # The deletes and updates below are done to update the incoming dictionary format to - # match to what the outgoing payload requirements mandate. - # Ex: 'vlanId' in the attach section of incoming payload needs to be changed to 'vlan' - # on the attach section of outgoing payload. + # The deletes and updates below are done to update the incoming + # dictionary format to align with the outgoing payload requirements. + # Ex: 'vlanId' in the attach section of the incoming payload needs to + # be changed to 'vlan' on the attach section of outgoing payload. del attach["vlanId"] del attach["switchSerialNo"] @@ -1409,88 +1428,50 @@ def get_have(self): attach.update({"isAttached": attach_state}) attach.update({"is_deploy": deployed}) - """ Get the VRF LITE extension template and update it to the attach['extensionvalues']""" - - """Get the IP/Interface that is connected to edge router can be get from below query""" - method = "GET" - path = self.paths["GET_VRF_SWITCH"].format( - self.fabric, attach["vrfName"], sn - ) - - lite_objects = dcnm_send(self.module, method, path) + # Get the VRF LITE extension template and update it + # with the attach['extensionvalues'] + lite_objects = self.get_vrf_lite_objects(attach) + msg = f"{self.class_name}.{method_name}: " if not lite_objects.get("DATA"): return for sdl in lite_objects["DATA"]: for epv in sdl["switchDetailsList"]: - if epv.get("extensionValues"): - ext_values = epv["extensionValues"] - ext_values = ast.literal_eval(ext_values) - if ext_values.get("VRF_LITE_CONN") is not None: - ext_values = ast.literal_eval( - ext_values["VRF_LITE_CONN"] - ) - extension_values = {} - extension_values["VRF_LITE_CONN"] = [] - - for ev in ext_values.get("VRF_LITE_CONN"): - vrflite_con = {} - - vrflite_con["VRF_LITE_CONN"] = [] - vrflite_con["VRF_LITE_CONN"].append({}) - vrflite_con["VRF_LITE_CONN"][0]["IF_NAME"] = ev[ - "IF_NAME" - ] - vrflite_con["VRF_LITE_CONN"][0]["DOT1Q_ID"] = str( - ev["DOT1Q_ID"] - ) - vrflite_con["VRF_LITE_CONN"][0]["IP_MASK"] = ev[ - "IP_MASK" - ] - vrflite_con["VRF_LITE_CONN"][0]["NEIGHBOR_IP"] = ev[ - "NEIGHBOR_IP" - ] - vrflite_con["VRF_LITE_CONN"][0]["IPV6_MASK"] = ev[ - "IPV6_MASK" - ] - vrflite_con["VRF_LITE_CONN"][0]["IPV6_NEIGHBOR"] = ( - ev["IPV6_NEIGHBOR"] - ) + if not epv.get("extensionValues"): + attach.update({"freeformConfig": ""}) + continue + ext_values = ast.literal_eval(epv["extensionValues"]) + if ext_values.get("VRF_LITE_CONN") is None: + continue + ext_values = ast.literal_eval(ext_values["VRF_LITE_CONN"]) + extension_values = {} + extension_values["VRF_LITE_CONN"] = [] - vrflite_con["VRF_LITE_CONN"][0][ - "AUTO_VRF_LITE_FLAG" - ] = "false" - vrflite_con["VRF_LITE_CONN"][0]["PEER_VRF_NAME"] = ( - ev["PEER_VRF_NAME"] - ) - vrflite_con["VRF_LITE_CONN"][0][ - "VRF_LITE_JYTHON_TEMPLATE" - ] = "Ext_VRF_Lite_Jython" + for ev in ext_values.get("VRF_LITE_CONN"): + ev_dict = copy.deepcopy(ev) + ev_dict.update({"AUTO_VRF_LITE_FLAG": "false"}) + ev_dict.update({"VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython"}) - if extension_values["VRF_LITE_CONN"]: - extension_values["VRF_LITE_CONN"][ - "VRF_LITE_CONN" - ].extend(vrflite_con["VRF_LITE_CONN"]) - else: - extension_values["VRF_LITE_CONN"] = vrflite_con + if extension_values["VRF_LITE_CONN"]: + extension_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend([ev_dict]) + else: + extension_values["VRF_LITE_CONN"] = {"VRF_LITE_CONN": [ev_dict]} - extension_values["VRF_LITE_CONN"] = json.dumps( - extension_values["VRF_LITE_CONN"] - ) + extension_values["VRF_LITE_CONN"] = json.dumps(extension_values["VRF_LITE_CONN"]) - ms_con = {} - ms_con["MULTISITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = json.dumps(ms_con) - e_values = json.dumps(extension_values).replace(" ", "") + ms_con = {} + ms_con["MULTISITE_CONN"] = [] + extension_values["MULTISITE_CONN"] = json.dumps(ms_con) + e_values = json.dumps(extension_values).replace(" ", "") - attach.update({"extensionValues": e_values}) + attach.update({"extensionValues": e_values}) ff_config = epv.get("freeformConfig", "") attach.update({"freeformConfig": ff_config}) - if dep_vrf: - upd_vrfs += dep_vrf + "," + if deploy_vrf: + upd_vrfs += deploy_vrf + "," have_attach = vrf_attach_objects["DATA"] @@ -1584,6 +1565,17 @@ def get_diff_delete(self): msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) + @staticmethod + def get_items_to_detach(attach_list): + detach_list = [] + for item in attach_list: + if "isAttached" in item: + if item["isAttached"]: + del item["isAttached"] + item.update({"deployment": False}) + detach_list.append(item) + return detach_list + diff_detach = [] diff_undeploy = {} diff_delete = {} @@ -1616,33 +1608,20 @@ def get_diff_delete(self): if not have_a: continue - to_del = [] - atch_h = have_a["lanAttachList"] - for a_h in atch_h: - if "isAttached" in a_h: - if a_h["isAttached"]: - del a_h["isAttached"] - a_h.update({"deployment": False}) - to_del.append(a_h) - if to_del: - have_a.update({"lanAttachList": to_del}) + detach_items = get_items_to_detach(have_a["lanAttachList"]) + if detach_items: + have_a.update({"lanAttachList": detach_items}) diff_detach.append(have_a) all_vrfs += have_a["vrfName"] + "," if all_vrfs: diff_undeploy.update({"vrfNames": all_vrfs[:-1]}) else: + for have_a in self.have_attach: - to_del = [] - atch_h = have_a["lanAttachList"] - for a_h in atch_h: - if "isAttached" in a_h: - if a_h["isAttached"]: - del a_h["isAttached"] - a_h.update({"deployment": False}) - to_del.append(a_h) - if to_del: - have_a.update({"lanAttachList": to_del}) + detach_items = get_items_to_detach(have_a["lanAttachList"]) + if detach_items: + have_a.update({"lanAttachList": detach_items}) diff_detach.append(have_a) all_vrfs += have_a["vrfName"] + "," @@ -1683,23 +1662,16 @@ def get_diff_override(self): diff_undeploy = self.diff_undeploy for have_a in self.have_attach: - found = next( - ( - vrf - for vrf in self.want_create - if vrf["vrfName"] == have_a["vrfName"] - ), - None, - ) + matching_vrf = (vrf for vrf in self.want_create if vrf["vrfName"] == have_a["vrfName"]) + found = next(matching_vrf, None) to_del = [] if not found: - atch_h = have_a["lanAttachList"] - for a_h in atch_h: - if "isAttached" in a_h: - if a_h["isAttached"]: - del a_h["isAttached"] - a_h.update({"deployment": False}) - to_del.append(a_h) + for item in have_a["lanAttachList"]: + if "isAttached" in item: + if item["isAttached"]: + del item["isAttached"] + item.update({"deployment": False}) + to_del.append(item) if to_del: have_a.update({"lanAttachList": to_del}) @@ -1795,9 +1767,6 @@ def get_diff_replace(self): atch_h = have_a["lanAttachList"] for a_h in atch_h: if "isAttached" in a_h: - msg = f"{self.class_name}.{method_name}: " - msg += f"TYPE: a_h[isAttached]: {type(a_h['isAttached'])}" - self.log.debug(msg) if not a_h["isAttached"]: continue del a_h["isAttached"] @@ -2043,7 +2012,7 @@ def get_diff_merge(self, replace=False): diff_create.append(want_c) for want_a in self.want_attach: - dep_vrf = "" + deploy_vrf = "" attach_found = False for have_a in self.have_attach: if want_a["vrfName"] == have_a["vrfName"]: @@ -2058,10 +2027,10 @@ def get_diff_merge(self, replace=False): diff_attach.append(base) if vrf: - dep_vrf = want_a["vrfName"] + deploy_vrf = want_a["vrfName"] else: if vrf or conf_changed.get(want_a["vrfName"], False): - dep_vrf = want_a["vrfName"] + deploy_vrf = want_a["vrfName"] if not attach_found and want_a.get("lanAttachList"): atch_list = [] @@ -2075,13 +2044,13 @@ def get_diff_merge(self, replace=False): base.update({"lanAttachList": atch_list}) diff_attach.append(base) if attach["is_deploy"]: - dep_vrf = want_a["vrfName"] + deploy_vrf = want_a["vrfName"] for atch in atch_list: atch["deployment"] = True - if dep_vrf: - all_vrfs += dep_vrf + "," + if deploy_vrf: + all_vrfs += deploy_vrf + "," if all_vrfs: diff_deploy.update({"vrfNames": all_vrfs[:-1]}) @@ -2097,7 +2066,9 @@ def get_diff_merge(self, replace=False): self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_create_update: {json.dumps(self.diff_create_update, indent=4)}" + msg += ( + f"self.diff_create_update: {json.dumps(self.diff_create_update, indent=4)}" + ) self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " diff --git a/tests/unit/modules/dcnm/fixtures/dcnm_vrf.json b/tests/unit/modules/dcnm/fixtures/dcnm_vrf.json index 34c2f081d..7d562b0eb 100644 --- a/tests/unit/modules/dcnm/fixtures/dcnm_vrf.json +++ b/tests/unit/modules/dcnm/fixtures/dcnm_vrf.json @@ -357,10 +357,12 @@ "vrfName": "test_vrf_1", "lanAttachList": [ { - "lanAttachState": "DEPLOYED" + "lanAttachState": "DEPLOYED", + "isLanAttached": false }, { - "lanAttachState": "DEPLOYED" + "lanAttachState": "DEPLOYED", + "isLanAttached": false } ] } @@ -393,10 +395,12 @@ "vrfName": "test_vrf_1", "lanAttachList": [ { - "lanAttachState": "NA" + "lanAttachState": "NA", + "isLanAttached": false }, { - "lanAttachState": "NA" + "lanAttachState": "NA", + "isLanAttached": false } ] } diff --git a/tests/unit/modules/dcnm/test_dcnm_vrf.py b/tests/unit/modules/dcnm/test_dcnm_vrf.py index 580ab5ad8..2e01adf83 100644 --- a/tests/unit/modules/dcnm/test_dcnm_vrf.py +++ b/tests/unit/modules/dcnm/test_dcnm_vrf.py @@ -17,14 +17,16 @@ __metaclass__ = type +import copy from unittest.mock import patch +from ansible_collections.cisco.dcnm.plugins.modules import dcnm_vrf + +from .dcnm_module import TestDcnmModule, loadPlaybookData, set_module_args + # from units.compat.mock import patch -from ansible_collections.cisco.dcnm.plugins.modules import dcnm_vrf -from .dcnm_module import TestDcnmModule, set_module_args, loadPlaybookData -import copy class TestDcnmVrfModule(TestDcnmModule): @@ -587,7 +589,7 @@ def test_dcnm_vrf_blank_fabric(self): result = self.execute_module(changed=False, failed=True) self.assertEqual( result.get("msg"), - "Fabric test_fabric missing on DCNM or does not have any switches", + "Fabric test_fabric missing on the controller or does not have any switches", ) def test_dcnm_vrf_get_have_failure(self): @@ -595,7 +597,7 @@ def test_dcnm_vrf_get_have_failure(self): dict(state="merged", fabric="test_fabric", config=self.playbook_config) ) result = self.execute_module(changed=False, failed=True) - self.assertEqual(result.get("msg"), "Fabric test_fabric not present on DCNM") + self.assertEqual(result.get("msg"), "Fabric test_fabric not present on the controller") def test_dcnm_vrf_merged_redeploy(self): set_module_args( @@ -707,7 +709,7 @@ def test_dcnm_vrf_merged_with_incorrect_vrfid(self): result = self.execute_module(changed=False, failed=True) self.assertEqual( result.get("msg"), - "vrf_id for vrf:test_vrf_1 cant be updated to a different value", + "DcnmVrf.diff_for_create: vrf_id for vrf test_vrf_1 cannot be updated to a different value", ) def test_dcnm_vrf_merged_lite_invalidrole(self): From 098d17f87552f12b8ada2cf4988acdfdb11d3654 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 4 Dec 2024 13:16:01 -1000 Subject: [PATCH 07/49] Rename var for readability --- plugins/modules/dcnm_vrf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 55a2183f4..5d91c9f30 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -1664,17 +1664,17 @@ def get_diff_override(self): for have_a in self.have_attach: matching_vrf = (vrf for vrf in self.want_create if vrf["vrfName"] == have_a["vrfName"]) found = next(matching_vrf, None) - to_del = [] + detach_list = [] if not found: for item in have_a["lanAttachList"]: if "isAttached" in item: if item["isAttached"]: del item["isAttached"] item.update({"deployment": False}) - to_del.append(item) + detach_list.append(item) - if to_del: - have_a.update({"lanAttachList": to_del}) + if detach_list: + have_a.update({"lanAttachList": detach_list}) diff_detach.append(have_a) all_vrfs += have_a["vrfName"] + "," From be4188292a37451c833040ebb620bde3704c204c Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 4 Dec 2024 13:18:28 -1000 Subject: [PATCH 08/49] Rename var for readability --- plugins/modules/dcnm_vrf.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 5d91c9f30..6673e26a9 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -1727,7 +1727,7 @@ def get_diff_replace(self): diff_deploy = self.diff_deploy for have_a in self.have_attach: - r_vrf_list = [] + replace_vrf_list = [] h_in_w = False for want_a in self.want_attach: if have_a["vrfName"] == want_a["vrfName"]: @@ -1751,7 +1751,7 @@ def get_diff_replace(self): if "isAttached" in a_h: del a_h["isAttached"] a_h.update({"deployment": False}) - r_vrf_list.append(a_h) + replace_vrf_list.append(a_h) break if not h_in_w: @@ -1771,20 +1771,20 @@ def get_diff_replace(self): continue del a_h["isAttached"] a_h.update({"deployment": False}) - r_vrf_list.append(a_h) + replace_vrf_list.append(a_h) - if r_vrf_list: + if replace_vrf_list: in_diff = False for d_attach in self.diff_attach: if have_a["vrfName"] == d_attach["vrfName"]: in_diff = True - d_attach["lanAttachList"].extend(r_vrf_list) + d_attach["lanAttachList"].extend(replace_vrf_list) break if not in_diff: r_vrf_dict = { "vrfName": have_a["vrfName"], - "lanAttachList": r_vrf_list, + "lanAttachList": replace_vrf_list, } diff_attach.append(r_vrf_dict) all_vrfs += have_a["vrfName"] + "," From 13e076eeb0998638f9b0eb6ffb44a39b6fc0d731 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 4 Dec 2024 14:34:13 -1000 Subject: [PATCH 09/49] dcnm_vrf: Avoid code duplication 1. find_dict_in_list_by_key_value() new method to generalize and consolidate duplicate code. 2. Remove a few cases of single-use vars. 3. Run though black --- plugins/modules/dcnm_vrf.py | 119 ++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 45 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 6673e26a9..b48c96810 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -593,16 +593,9 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import ( - dcnm_get_ip_addr_info, - dcnm_get_url, - dcnm_send, - dcnm_version_supported, - get_fabric_details, - get_fabric_inventory_details, - get_ip_sn_dict, - get_ip_sn_fabric_dict, - validate_list_of_dicts, -) + dcnm_get_ip_addr_info, dcnm_get_url, dcnm_send, dcnm_version_supported, + get_fabric_details, get_fabric_inventory_details, get_ip_sn_dict, + get_ip_sn_fabric_dict, validate_list_of_dicts) # from ..module_utils.common.controller_version import ControllerVersion from ..module_utils.common.log_v2 import Log @@ -688,6 +681,47 @@ def __init__(self, module): msg = f"{self.class_name}.__init__(): DONE" self.log.debug(msg) + @staticmethod + def find_dict_in_list_by_key_value( + search: list, key: str, value: str + ) -> dict | None: + """ + # Summary + + Find a dictionary in a list of dictionaries. + + + ## Raises + + None + + ## Parameters + + - search: A list of dict + - key: The key to lookup in each dict + - value: The desired matching value for key + + ## Returns + + Either the first matching dict or None + + ## Usage + + ```python + content = [{"foo": "bar"}, {"foo": "baz"}] + + match = find_dict_in_list_by_key_value(search=content, key="foo", value="baz") + print(f"{match}") + # -> {"foo": "baz"} + + match = find_dict_in_list_by_key_value(search=content, key="foo", value="bingo") + print(f"{match}") + # -> None + ``` + """ + match = (d for d in search if d[key] == value) + return next(match, None) + def to_bool(self, key, dict_with_key): """ # Summary @@ -1451,14 +1485,22 @@ def get_have(self): for ev in ext_values.get("VRF_LITE_CONN"): ev_dict = copy.deepcopy(ev) ev_dict.update({"AUTO_VRF_LITE_FLAG": "false"}) - ev_dict.update({"VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython"}) + ev_dict.update( + {"VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython"} + ) if extension_values["VRF_LITE_CONN"]: - extension_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend([ev_dict]) + extension_values["VRF_LITE_CONN"][ + "VRF_LITE_CONN" + ].extend([ev_dict]) else: - extension_values["VRF_LITE_CONN"] = {"VRF_LITE_CONN": [ev_dict]} + extension_values["VRF_LITE_CONN"] = { + "VRF_LITE_CONN": [ev_dict] + } - extension_values["VRF_LITE_CONN"] = json.dumps(extension_values["VRF_LITE_CONN"]) + extension_values["VRF_LITE_CONN"] = json.dumps( + extension_values["VRF_LITE_CONN"] + ) ms_con = {} ms_con["MULTISITE_CONN"] = [] @@ -1585,24 +1627,16 @@ def get_items_to_detach(attach_list): if self.config: for want_c in self.want_create: - if not next( - ( - have_c - for have_c in self.have_create - if have_c["vrfName"] == want_c["vrfName"] - ), - None, + + if not self.find_dict_in_list_by_key_value( + search=self.have_create, key="vrfName", value=want_c["vrfName"] ): continue + diff_delete.update({want_c["vrfName"]: "DEPLOYED"}) - have_a = next( - ( - attach - for attach in self.have_attach - if attach["vrfName"] == want_c["vrfName"] - ), - None, + have_a = self.find_dict_in_list_by_key_value( + search=self.have_attach, key="vrfName", value=want_c["vrfName"] ) if not have_a: @@ -1662,8 +1696,10 @@ def get_diff_override(self): diff_undeploy = self.diff_undeploy for have_a in self.have_attach: - matching_vrf = (vrf for vrf in self.want_create if vrf["vrfName"] == have_a["vrfName"]) - found = next(matching_vrf, None) + found = self.find_dict_in_list_by_key_value( + search=self.want_create, key="vrfName", value=have_a["vrfName"] + ) + detach_list = [] if not found: for item in have_a["lanAttachList"]: @@ -1732,17 +1768,15 @@ def get_diff_replace(self): for want_a in self.want_attach: if have_a["vrfName"] == want_a["vrfName"]: h_in_w = True - atch_h = have_a["lanAttachList"] - atch_w = want_a.get("lanAttachList") - for a_h in atch_h: + for a_h in have_a["lanAttachList"]: if "isAttached" in a_h: if not a_h["isAttached"]: continue a_match = False - if atch_w: - for a_w in atch_w: + if want_a.get("lanAttachList"): + for a_w in want_a.get("lanAttachList"): if a_h["serialNumber"] == a_w["serialNumber"]: # Have is already in diff, no need to continue looking for it. a_match = True @@ -1755,14 +1789,10 @@ def get_diff_replace(self): break if not h_in_w: - found = next( - ( - vrf - for vrf in self.want_create - if vrf["vrfName"] == have_a["vrfName"] - ), - None, + found = self.find_dict_in_list_by_key_value( + search=self.want_create, key="vrfName", value=have_a["vrfName"] ) + if found: atch_h = have_a["lanAttachList"] for a_h in atch_h: @@ -2109,9 +2139,8 @@ def format_diff(self): for want_d in diff_create: - found_a = next( - (vrf for vrf in diff_attach if vrf["vrfName"] == want_d["vrfName"]), - None, + found_a = self.find_dict_in_list_by_key_value( + search=diff_attach, key="vrfName", value=want_d["vrfName"] ) found_c = want_d From 6a6de04ab1ee264bbb701bc50b54b5156cd35cfc Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 4 Dec 2024 15:15:34 -1000 Subject: [PATCH 10/49] Remove TODO comment I opened an issue to track what this comment describes, so can remove the comment from the module. https://github.com/CiscoDevNet/ansible-dcnm/issues/352 --- plugins/modules/dcnm_vrf.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index b48c96810..d2f05e236 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -17,30 +17,9 @@ __metaclass__ = type __author__ = ( - "Shrishail Kariyappanavar, Karthik Babu Harichandra Babu, Praveen Ramoorthy" + "Shrishail Kariyappanavar, Karthik Babu Harichandra Babu, Praveen Ramoorthy, Allen Robel" ) -# TODO: arobel: If Fabric -> Resources -> Per VRF Per VTEP Loopback IPv4 Auto-Provisioning -# is enabled, and dcnm_vrf merged-state playbook is run, the following error is seen. -# fatal: [172.22.150.244]: FAILED! => -# { -# "changed": false, -# "msg": -# { -# "DATA": { -# "Error": "Internal Server Error", -# "message": "per vrf level loopback is enabled and hence not allowed to clear the loopback ID or IP", -# "path": "/rest/top-down/fabrics/MSD/vrfs/attachments", -# "status": "500", -# "timestamp": "2024-11-28 01:35:15.164"}, -# "MESSAGE": "Internal Server Error", -# "METHOD": "POST", -# "REQUEST_PATH": "https://10.1.1.1:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/MSD/vrfs/attachments", -# "RETURN_CODE": 500 -# } -# } -# } - DOCUMENTATION = """ --- module: dcnm_vrf From 12f9e5358dd8d45c8b76bb98499b9d95c90c4df0 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 4 Dec 2024 15:54:58 -1000 Subject: [PATCH 11/49] dcnm_vrf: leverage get_vrf_lite_objects() everywhere 1. Replace several bits that can be replaced with a call to get_vrf_lite_objects(). 2. Fix a few pylint f-string complaints. There are many more of these, which we'll address in the next commit. One of these required a change to an associated unit test. --- plugins/modules/dcnm_vrf.py | 75 ++++++++++++------------ tests/unit/modules/dcnm/test_dcnm_vrf.py | 3 +- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index d2f05e236..705e6dc13 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -16,9 +16,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -__author__ = ( - "Shrishail Kariyappanavar, Karthik Babu Harichandra Babu, Praveen Ramoorthy, Allen Robel" -) +__author__ = "Shrishail Kariyappanavar, Karthik Babu Harichandra Babu, Praveen Ramoorthy, Allen Robel" DOCUMENTATION = """ --- @@ -1107,8 +1105,9 @@ def dict_values_differ(self, want_template, have_template, skip_keys=[]) -> bool have_value = str(have_template[key]).lower() if want_value != have_value: msg = f"{self.class_name}.{method_name}: " - msg += f"DIFFERS: key {key} want_value {want_value} != have_value {have_value}. " - msg += f"returning True" + msg += f"DIFFERS: key {key} " + msg += f"want_value {want_value} != have_value {have_value}. " + msg += "returning True" self.log.debug(msg) return True return False @@ -1997,10 +1996,11 @@ def get_diff_merge(self, replace=False): break if not vrf_id: - self.module.fail_json( - msg="Unable to generate vrfId for vrf: {0} " - "under fabric: {1}".format(want_c["vrfName"], self.fabric) - ) + # arobel: TODO: This is not covered in UT + msg = f"{self.class_name}.{method_name}: " + msg += f"Unable to generate vrfId for vrf: {want_c['vrfName']} " + msg += f"under fabric: {self.fabric}" + self.module.fail_json(msg=msg) create_path = self.paths["GET_VRF"].format(self.fabric) @@ -2347,12 +2347,16 @@ def get_diff_query(self): attach_list = vrf_attach["lanAttachList"] for attach in attach_list: - path = self.paths["GET_VRF_SWITCH"].format( - self.fabric, - attach["vrfName"], - attach["switchSerialNo"], + # copy attach and update it with the keys that + # get_vrf_lite_objects() expects. + attach_copy = copy.deepcopy(attach) + attach_copy.update({"fabric": self.fabric}) + attach_copy.update( + {"serialNumber": attach["switchSerialNo"]} + ) + lite_objects = self.get_vrf_lite_objects( + attach_copy ) - lite_objects = dcnm_send(self.module, method, path) if not lite_objects.get("DATA"): return item["attach"].append(lite_objects.get("DATA")[0]) @@ -2394,11 +2398,13 @@ def get_diff_query(self): attach_list = vrf_attach["lanAttachList"] for attach in attach_list: - path = self.paths["GET_VRF_SWITCH"].format( - self.fabric, attach["vrfName"], attach["switchSerialNo"] - ) + # copy attach and update it with the keys that + # get_vrf_lite_objects() expects. + attach_copy = copy.deepcopy(attach) + attach_copy.update({"fabric": self.fabric}) + attach_copy.update({"serialNumber": attach["switchSerialNo"]}) + lite_objects = self.get_vrf_lite_objects(attach_copy) - lite_objects = dcnm_send(self.module, method, path) if not lite_objects.get("DATA"): return item["attach"].append(lite_objects.get("DATA")[0]) @@ -2596,19 +2602,12 @@ def push_to_remote(self, is_rollback=False): role = self.inventory_data[ip].get("switchRole") r = re.search(r"\bborder\b", role.lower()) if not r: - msg = "VRF LITE cannot be attached to switch {0} with role {1}".format( - ip, role - ) + msg = f"{self.class_name}.{method_name}: " + msg += "VRF LITE cannot be attached to " + msg += f"switch {ip} with role {role}" self.module.fail_json(msg=msg) - """Get the IP/Interface that is connected to edge router can be get from below query""" - method = "GET" - path = self.paths["GET_VRF_SWITCH"].format( - self.fabric, v_a["vrfName"], v_a["serialNumber"] - ) - - lite_objects = dcnm_send(self.module, method, path) - + lite_objects = self.get_vrf_lite_objects(v_a) if not lite_objects.get("DATA"): return @@ -2703,10 +2702,11 @@ def push_to_remote(self, is_rollback=False): if ext_values is None: for ip, ser in self.ip_sn.items(): + # arobel TODO: Not covered by UT if ser == v_a["serialNumber"]: - msg = "There is no VRF LITE capable interface on this switch {0}".format( - ip - ) + msg = f"{self.class_name}.{method_name}: " + msg += "No VRF LITE capable interfaces found " + msg += f"on this switch {ip}" self.module.fail_json(msg=msg) else: extension_values["VRF_LITE_CONN"] = json.dumps( @@ -2793,11 +2793,11 @@ def wait_for_vrf_del_ready(self): vlan_id = atch.get("vlanId", "unknown") msg = f"{self.class_name}.{method_name}: " msg += f"Network attachments associated with vrf {vrf_name} " - msg += f"must be removed (e.g. using the dcnm_network module) " - msg += f"prior to deleting the vrf. " + msg += "must be removed (e.g. using the dcnm_network module) " + msg += "prior to deleting the vrf. " msg += f"Details: fabric_name: {fabric_name}, " msg += f"vrf_name: {vrf_name}. " - msg += f"Network attachments found on " + msg += "Network attachments found on " msg += f"switch_ip: {switch_ip}, " msg += f"switch_name: {switch_name}, " msg += f"vlan_id: {vlan_id}" @@ -2908,9 +2908,8 @@ def validate_input(self): msg = "ip_address is mandatory under attach parameters" else: if state == "merged" or state == "replaced": - msg = "config: element is mandatory for this state {0}".format( - state - ) + msg = f"{self.class_name}.{method_name}: " + msg += f"config element is mandatory for {state} state" if msg: self.module.fail_json(msg=msg) diff --git a/tests/unit/modules/dcnm/test_dcnm_vrf.py b/tests/unit/modules/dcnm/test_dcnm_vrf.py index 2e01adf83..2c09b3bcb 100644 --- a/tests/unit/modules/dcnm/test_dcnm_vrf.py +++ b/tests/unit/modules/dcnm/test_dcnm_vrf.py @@ -1279,8 +1279,9 @@ def test_dcnm_vrf_validation(self): def test_dcnm_vrf_validation_no_config(self): set_module_args(dict(state="merged", fabric="test_fabric", config=[])) result = self.execute_module(changed=False, failed=True) + msg = "DcnmVrf.validate_input: config element is mandatory for merged state" self.assertEqual( - result["msg"], "config: element is mandatory for this state merged" + result["msg"], msg ) def test_dcnm_vrf_12check_mode(self): From bbbf28510652d6810d6d7196c849d3add900f174 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 4 Dec 2024 16:42:41 -1000 Subject: [PATCH 12/49] Appease pylint f-string complaints, more... 1. Appease pylint f-string complaints 2. optimize a couple conditionals 3. Change an "== True" to the preferred "is True" 4. Add a few TODO comments --- plugins/modules/dcnm_vrf.py | 77 +++++++++++------------- tests/unit/modules/dcnm/test_dcnm_vrf.py | 5 +- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 705e6dc13..d8ee2b594 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -647,7 +647,7 @@ def __init__(self, module): else: self.paths = self.dcnm_vrf_paths[self.dcnm_version] - self.result = dict(changed=False, diff=[], response=[]) + self.result = {"changed": False, "diff": [], "response": []} self.failed_to_rollback = False self.WAIT_TIME_FOR_DELETE_LOOP = 5 # in seconds @@ -1878,14 +1878,11 @@ def get_diff_merge(self, replace=False): ) if missing_fabric or not_ok: - msg1 = "Fabric {0} not present on DCNM".format(self.fabric) - msg2 = ( - "Unable to generate vrfId for vrf: {0} " - "under fabric: {1}".format( - want_c["vrfName"], self.fabric - ) - ) - + # arobel: TODO: Not covered by UT + msg0 = f"{self.class_name}.{method_name}: " + msg1 = f"{msg0} Fabric {self.fabric} not present on the controller" + msg2 = f"{msg0} Unable to generate vrfId for vrf " + msg2 += f"{want_c['vrfName']} under fabric {self.fabric}" self.module.fail_json(msg=msg1 if missing_fabric else msg2) if not vrf_id_obj["DATA"]: @@ -1896,8 +1893,10 @@ def get_diff_merge(self, replace=False): elif self.dcnm_version >= 12: vrf_id = vrf_id_obj["DATA"].get("l3vni") else: - msg = "Unsupported DCNM version: version {0}".format( - self.dcnm_version + # arobel: TODO: Not covered by UT + msg = f"{self.class_name}.{method_name}: " + msg += ( + f"Unsupported controller version: {self.dcnm_version}" ) self.module.fail_json(msg) @@ -1996,7 +1995,7 @@ def get_diff_merge(self, replace=False): break if not vrf_id: - # arobel: TODO: This is not covered in UT + # arobel: TODO: Not covered by UT msg = f"{self.class_name}.{method_name}: " msg += f"Unable to generate vrfId for vrf: {want_c['vrfName']} " msg += f"under fabric: {self.fabric}" @@ -2009,6 +2008,7 @@ def get_diff_merge(self, replace=False): if self.module.check_mode: continue + # arobel: TODO: The below is not covered by UT resp = dcnm_send( self.module, method, create_path, json.dumps(want_c) ) @@ -2295,8 +2295,10 @@ def get_diff_query(self): return if missing_fabric or not_ok: - msg1 = "Fabric {0} not present on DCNM".format(self.fabric) - msg2 = "Unable to find VRFs under fabric: {0}".format(self.fabric) + # arobel: TODO: Not covered by UT + msg0 = f"{self.class_name}.{method_name}:" + msg1 = f"{msg0} Fabric {self.fabric} not present on the controller" + msg2 = f"{msg0} Unable to find VRFs under fabric: {self.fabric}" self.module.fail_json(msg=msg1 if missing_fabric else msg2) if not vrf_objects["DATA"]: @@ -2326,16 +2328,14 @@ def get_diff_query(self): ) if missing_fabric or not_ok: - msg1 = "Fabric {0} not present on DCNM".format(self.fabric) - msg2 = ( - "Unable to find attachments for " - "vrfs: {0} under fabric: {1}".format( - vrf["vrfName"], self.fabric - ) + # arobel: TODO: Not covered by UT + msg0 = f"{self.class_name}.{method_name}:" + msg1 = f"{msg0} Fabric {self.fabric} not present on the controller" + msg2 = f"{msg0} Unable to find attachments for " + msg2 += ( + f"vrfs: {vrf['vrfName']} under fabric: {self.fabric}" ) - self.module.fail_json(msg=msg1 if missing_fabric else msg2) - return if not vrf_attach_objects["DATA"]: return @@ -2378,13 +2378,9 @@ def get_diff_query(self): missing_fabric, not_ok = self.handle_response(vrf_objects, "query_dcnm") if missing_fabric or not_ok: - msg1 = "Fabric {0} not present on DCNM".format(self.fabric) - msg2 = ( - "Unable to find attachments for " - "vrfs: {0} under fabric: {1}".format( - vrf["vrfName"], self.fabric - ) - ) + msg1 = f"Fabric {self.fabric} not present on DCNM" + msg2 = "Unable to find attachments for " + msg2 += f"vrfs: {vrf['vrfName']} under fabric: {self.fabric}" self.module.fail_json(msg=msg1 if missing_fabric else msg2) return @@ -2422,7 +2418,7 @@ def push_to_remote(self, is_rollback=False): method = "PUT" if self.diff_create_update: for vrf in self.diff_create_update: - update_path = path + "/{0}".format(vrf["vrfName"]) + update_path = f"{path}/{vrf['vrfName']}" resp = dcnm_send(self.module, method, update_path, json.dumps(vrf)) self.result["response"].append(resp) fail, self.result["changed"] = self.handle_response(resp, "create") @@ -2497,9 +2493,9 @@ def push_to_remote(self, is_rollback=False): self.failure(resp) if del_failure: - self.result["response"].append( - "Deletion of vrfs {0} has failed".format(del_failure[:-1]) - ) + msg = f"{self.class_name}.{method_name}: " + msg += f"Deletion of vrfs {del_failure[:-1]} has failed" + self.result["response"].append(msg) self.module.fail_json(msg=self.result) method = "POST" @@ -2514,11 +2510,10 @@ def push_to_remote(self, is_rollback=False): vlan_data = dcnm_send(self.module, "GET", vlan_path) if vlan_data["RETURN_CODE"] != 200: - self.module.fail_json( - msg="Failure getting autogenerated vlan_id {0}".format( - vlan_data - ) - ) + msg = f"{self.class_name}.{method_name}: " + msg += f"Failure getting autogenerated vlan_id {vlan_data}" + self.module.fail_json(msg=msg) + vlanId = vlan_data["DATA"] t_conf = { @@ -2784,7 +2779,7 @@ def wait_for_vrf_del_ready(self): break if ( atch["lanAttachState"] == "DEPLOYED" - and atch["isLanAttached"] == True + and atch["isLanAttached"] is True ): vrf_name = atch.get("vrfName", "unknown") fabric_name = atch.get("fabricName", "unknown") @@ -2819,7 +2814,7 @@ def validate_input(self): state = self.params["state"] - if state == "merged" or state == "overridden" or state == "replaced": + if state in ("merged", "overridden", "replaced"): vrf_spec = dict( vrf_name=dict(required=True, type="str", length_max=32), @@ -2907,7 +2902,7 @@ def validate_input(self): if "ip_address" not in attach: msg = "ip_address is mandatory under attach parameters" else: - if state == "merged" or state == "replaced": + if state in ("merged", "replaced"): msg = f"{self.class_name}.{method_name}: " msg += f"config element is mandatory for {state} state" diff --git a/tests/unit/modules/dcnm/test_dcnm_vrf.py b/tests/unit/modules/dcnm/test_dcnm_vrf.py index 2c09b3bcb..3bb26cbf1 100644 --- a/tests/unit/modules/dcnm/test_dcnm_vrf.py +++ b/tests/unit/modules/dcnm/test_dcnm_vrf.py @@ -1146,9 +1146,8 @@ def test_dcnm_vrf_delete_failure(self): dict(state="deleted", fabric="test_fabric", config=self.playbook_config) ) result = self.execute_module(changed=False, failed=True) - self.assertEqual( - result["msg"]["response"][2], "Deletion of vrfs test_vrf_1 has failed" - ) + msg = "DcnmVrf.push_to_remote: Deletion of vrfs test_vrf_1 has failed" + self.assertEqual(result["msg"]["response"][2], msg) def test_dcnm_vrf_query(self): set_module_args( From da54fa1103577ab9fb444f4009e50c442d83c1c9 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 5 Dec 2024 08:37:13 -1000 Subject: [PATCH 13/49] test_log_v2.py: Temporarily disable unit tests Unit tests pass locally if Ithe tests in the following file are disabled: ~/test/unit/module_utils/common/test_log_v2.py. Temporarily disabling these to see if the same is seen when running the unit tests on Github. If the same is seen, will debug why this is happening. --- .../common/{test_log_v2.py => __DISABLED_test_log_v2.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/unit/module_utils/common/{test_log_v2.py => __DISABLED_test_log_v2.py} (100%) diff --git a/tests/unit/module_utils/common/test_log_v2.py b/tests/unit/module_utils/common/__DISABLED_test_log_v2.py similarity index 100% rename from tests/unit/module_utils/common/test_log_v2.py rename to tests/unit/module_utils/common/__DISABLED_test_log_v2.py From c8c578beb184a08f9a236b8cdcf15101d11fb2ef Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 5 Dec 2024 08:45:43 -1000 Subject: [PATCH 14/49] Appease pylint Fix bare-except and dangerous-default-value errors. --- plugins/modules/dcnm_vrf.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index d8ee2b594..32afe2061 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -917,11 +917,12 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): attach_list.append(want) if want["is_deploy"]: deploy_vrf = True - except: + except KeyError as error: msg = f"{self.class_name}.{method_name}: " msg += "Unexpected values for " msg += f"isAttached ({want.get('isAttached')}) " - msg += f"and/or is_deploy ({want.get('is_deploy')})" + msg += f"and/or is_deploy ({want.get('is_deploy')}) " + msg += f"Details: {error}" self.log.debug(msg) self.module.fail_json(msg=msg) @@ -1085,7 +1086,7 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): self.log.debug(msg) return attach - def dict_values_differ(self, want_template, have_template, skip_keys=[]) -> bool: + def dict_values_differ(self, want_template, have_template, skip_keys=None) -> bool: """ # Summary @@ -1098,6 +1099,9 @@ def dict_values_differ(self, want_template, have_template, skip_keys=[]) -> bool msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) + if skip_keys is None: + skip_keys = [] + for key in want_template.keys(): if key in skip_keys: continue From 14da7d73f0f2d7efd9312d2992cfcf32c0c3bf37 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 5 Dec 2024 08:56:38 -1000 Subject: [PATCH 15/49] Fix pep8 too-many-blank-lines test_dcnm_vrf.py: Removed two (out of four) contiguous blank lines. --- tests/unit/modules/dcnm/test_dcnm_vrf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/modules/dcnm/test_dcnm_vrf.py b/tests/unit/modules/dcnm/test_dcnm_vrf.py index 3bb26cbf1..290b285db 100644 --- a/tests/unit/modules/dcnm/test_dcnm_vrf.py +++ b/tests/unit/modules/dcnm/test_dcnm_vrf.py @@ -27,8 +27,6 @@ # from units.compat.mock import patch - - class TestDcnmVrfModule(TestDcnmModule): module = dcnm_vrf From 1e028a28ec2147406b6b6dfea55f555919530d95 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 5 Dec 2024 09:06:08 -1000 Subject: [PATCH 16/49] Remove python 3.9 incompatible type hint python 3.9 doesn't like: def find_dict_in_list_by_key_value( ... ) -> dict | None: Removed the type hint: def find_dict_in_list_by_key_value( ... ): --- plugins/modules/dcnm_vrf.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 32afe2061..44ff2ad53 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -659,9 +659,7 @@ def __init__(self, module): self.log.debug(msg) @staticmethod - def find_dict_in_list_by_key_value( - search: list, key: str, value: str - ) -> dict | None: + def find_dict_in_list_by_key_value(search: list, key: str, value: str): """ # Summary From ee179b539385baf9e03332d83c8624db8084f5ca Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 5 Dec 2024 13:18:00 -1000 Subject: [PATCH 17/49] Re-enable test_log_v2.py unit tests and "fix" UT errors If we fail_json(), or even if we sys.exit() in main() logging setup, the unit tests fail. The failure is a KeyError in logging.config.dictConfig when disabling logging in log_v2.py: def disable_logging(self): logger = logging.getLogger() for handler in logger.handlers.copy(): try: logger.removeHandler(handler) except ValueError: # if handler already removed pass logger.addHandler(logging.NullHandler()) logger.propagate = False Above, the KeyError happens here logger.removeHandler(handler) The value of handler when this happens is "standard" I'm not sure why this happens ONLY when the log_v2.py unit tests are run prior to the dcnm_vrf.py unit tests (running these tests separately works). For now, a "fix" is to pass in the except portion of the try/except block in dcnm_vrf.py main(). def main(): try: log = Log() log.commit() except (TypeError, ValueError) as error: pass Will investigate further, but the above works, and logging is enabled with no issue in normal use. Am renaming __DISABLE_test_log_v2.py back to test_log_v2.py --- plugins/modules/dcnm_vrf.py | 19 ++++++++++--------- ...DISABLED_test_log_v2.py => test_log_v2.py} | 0 2 files changed, 10 insertions(+), 9 deletions(-) rename tests/unit/module_utils/common/{__DISABLED_test_log_v2.py => test_log_v2.py} (100%) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 44ff2ad53..c252a6cd4 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -566,6 +566,7 @@ import json import logging import re +import sys import time from ansible.module_utils.basic import AnsibleModule @@ -604,6 +605,8 @@ class DcnmVrf: } def __init__(self, module): + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") self.module = module self.params = module.params @@ -652,9 +655,6 @@ def __init__(self, module): self.failed_to_rollback = False self.WAIT_TIME_FOR_DELETE_LOOP = 5 # in seconds - self.class_name = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") - msg = f"{self.class_name}.__init__(): DONE" self.log.debug(msg) @@ -3116,6 +3116,13 @@ def failure(self, resp): def main(): """main entry point for module execution""" + # Logging setup + try: + log = Log() + log.commit() + except (TypeError, ValueError) as error: + pass + element_spec = dict( fabric=dict(required=True, type="str"), config=dict(required=False, type="list", elements="dict"), @@ -3127,12 +3134,6 @@ def main(): module = AnsibleModule(argument_spec=element_spec, supports_check_mode=True) - # Logging setup - try: - log = Log() - log.commit() - except (TypeError, ValueError) as error: - module.fail_json(str(error)) dcnm_vrf = DcnmVrf(module) diff --git a/tests/unit/module_utils/common/__DISABLED_test_log_v2.py b/tests/unit/module_utils/common/test_log_v2.py similarity index 100% rename from tests/unit/module_utils/common/__DISABLED_test_log_v2.py rename to tests/unit/module_utils/common/test_log_v2.py From 0c980b3d2f18b59afe6387fc30913b66c2364648 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 5 Dec 2024 13:26:51 -1000 Subject: [PATCH 18/49] Appease linters Remove unused import (sys, added to test fixes for the unit test failures). Remove extra lines. --- plugins/modules/dcnm_vrf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index c252a6cd4..e0862abe9 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -566,7 +566,6 @@ import json import logging import re -import sys import time from ansible.module_utils.basic import AnsibleModule @@ -3134,7 +3133,6 @@ def main(): module = AnsibleModule(argument_spec=element_spec, supports_check_mode=True) - dcnm_vrf = DcnmVrf(module) if not dcnm_vrf.ip_sn: From b401ab8c5c20a12410549b5302cc51d7f2838b6d Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 5 Dec 2024 16:55:26 -1000 Subject: [PATCH 19/49] Update another conditional Modify another OR-conditional to use the preferred: if X "in" (X, Y, Z): --- plugins/modules/dcnm_vrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index e0862abe9..4f745223b 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -954,7 +954,7 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): role = self.inventory_data[attach["ip_address"]].get("switchRole") - if role.lower() == "spine" or role.lower() == "super spine": + if role.lower() in ("spine", "super spine"): msg = f"VRFs cannot be attached to switch {attach['ip_address']} " msg += f"with role {role}" self.module.fail_json(msg=msg) From 978b0d25ee164b25650791701d17ded38f42e68b Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 5 Dec 2024 17:21:21 -1000 Subject: [PATCH 20/49] dcnm_vrf: dict_values_differ() use generic names Use generic names for the two dicts. --- plugins/modules/dcnm_vrf.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 4f745223b..4015e0d8d 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -1083,7 +1083,7 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): self.log.debug(msg) return attach - def dict_values_differ(self, want_template, have_template, skip_keys=None) -> bool: + def dict_values_differ(self, dict1, dict2, skip_keys=None) -> bool: """ # Summary @@ -1099,15 +1099,15 @@ def dict_values_differ(self, want_template, have_template, skip_keys=None) -> bo if skip_keys is None: skip_keys = [] - for key in want_template.keys(): + for key in dict1.keys(): if key in skip_keys: continue - want_value = str(want_template[key]).lower() - have_value = str(have_template[key]).lower() - if want_value != have_value: + dict1_value = str(dict1[key]).lower() + dict2_value = str(dict2[key]).lower() + if dict1_value != dict2_value: msg = f"{self.class_name}.{method_name}: " - msg += f"DIFFERS: key {key} " - msg += f"want_value {want_value} != have_value {have_value}. " + msg += f"Values differ: key {key} " + msg += f"dict1_value {dict1_value} != dict2_value {dict2_value}. " msg += "returning True" self.log.debug(msg) return True From 75a24d5eac8a335e0ad0c42362e564424936cc4e Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 6 Dec 2024 07:54:57 -1000 Subject: [PATCH 21/49] dcnm_vrf: Address mwiebe review comments 1. compare_properties: refactor comparison in diff_for_attach_deploy() using this new method. 2. diff_for_attach_deploy(): Leverate to_bool() to add dictionary access protection and remove try/except block. --- plugins/modules/dcnm_vrf.py | 82 +++++++++++++++---------------------- 1 file changed, 32 insertions(+), 50 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 4015e0d8d..681ce3156 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -726,6 +726,19 @@ def to_bool(self, key, dict_with_key): msg += "is not convertable to boolean" self.module.fail_json(msg=msg) + @staticmethod + def compare_properties(dict1, dict2, property_list): + """ + Given two dictionaries and a list of keys: + + - Return True if all property values match. + - Return False otherwise + """ + for prop in property_list: + if dict1.get(prop) != dict2.get(prop): + return False + return True + def diff_for_attach_deploy(self, want_a, have_a, replace=False): method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" @@ -803,6 +816,14 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): # this switch break + vrf_lite_properties = [ + "DOT1Q_ID", + "IP_MASK", + "IPV6_MASK", + "IPV6_NEIGHBOR", + "NEIGHBOR_IP", + "PEER_VRF_NAME", + ] for wlite in want_e["VRF_LITE_CONN"]: for hlite in have_e["VRF_LITE_CONN"]: found = False @@ -811,41 +832,11 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): continue found = True interface_match = True - if wlite["DOT1Q_ID"]: - if wlite["DOT1Q_ID"] != hlite["DOT1Q_ID"]: - found = False - break - - if wlite["IP_MASK"]: - if wlite["IP_MASK"] != hlite["IP_MASK"]: - found = False - break - - if wlite["NEIGHBOR_IP"]: - if wlite["NEIGHBOR_IP"] != hlite["NEIGHBOR_IP"]: - found = False - break - - if wlite["IPV6_MASK"]: - if wlite["IPV6_MASK"] != hlite["IPV6_MASK"]: - found = False - break - - if wlite["IPV6_NEIGHBOR"]: - if ( - wlite["IPV6_NEIGHBOR"] - != hlite["IPV6_NEIGHBOR"] - ): - found = False - break - - if wlite["PEER_VRF_NAME"]: - if ( - wlite["PEER_VRF_NAME"] - != hlite["PEER_VRF_NAME"] - ): - found = False - break + if not self.compare_properties( + wlite, hlite, vrf_lite_properties + ): + found = False + break if found: break @@ -907,21 +898,12 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): break if not found: - try: - if want["isAttached"]: - del want["isAttached"] - want["deployment"] = True - attach_list.append(want) - if want["is_deploy"]: - deploy_vrf = True - except KeyError as error: - msg = f"{self.class_name}.{method_name}: " - msg += "Unexpected values for " - msg += f"isAttached ({want.get('isAttached')}) " - msg += f"and/or is_deploy ({want.get('is_deploy')}) " - msg += f"Details: {error}" - self.log.debug(msg) - self.module.fail_json(msg=msg) + if self.to_bool("isAttached", want): + del want["isAttached"] + want["deployment"] = True + attach_list.append(want) + if self.to_bool("is_deploy", want): + deploy_vrf = True msg = f"{self.class_name}.{method_name}: " msg += f"deploy_vrf: {deploy_vrf}, " From f747767bd885f02ea9f3aa9e8c7fce86e1aa1b5d Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 6 Dec 2024 07:58:22 -1000 Subject: [PATCH 22/49] Address mwiebe coments part 2 1. Remove commented imports. 2. main(): Remove unused var (error) --- plugins/modules/dcnm_vrf.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 681ce3156..f6d669d0f 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -574,15 +574,8 @@ get_fabric_details, get_fabric_inventory_details, get_ip_sn_dict, get_ip_sn_fabric_dict, validate_list_of_dicts) -# from ..module_utils.common.controller_version import ControllerVersion from ..module_utils.common.log_v2 import Log -# from ..module_utils.common.response_handler import ResponseHandler -# from ..module_utils.common.rest_send_v2 import RestSend -# from ..module_utils.common.results import Results -# from ..module_utils.common.sender_dcnm import Sender -# from ..module_utils.fabric.fabric_details_v2 import FabricDetailsByName - class DcnmVrf: @@ -3101,7 +3094,7 @@ def main(): try: log = Log() log.commit() - except (TypeError, ValueError) as error: + except (TypeError, ValueError): pass element_spec = dict( From dbfe05737a95b3886541edd0cc5fd4173d4b73f3 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 6 Dec 2024 13:20:41 -1000 Subject: [PATCH 23/49] dcnm_vrf: Protect dictionary access Fix KeyError hit during IT. --- plugins/modules/dcnm_vrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index f6d669d0f..7da5504a6 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -751,7 +751,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if want["serialNumber"] == have["serialNumber"]: # handle instanceValues first want.update( - {"freeformConfig": have["freeformConfig"]} + {"freeformConfig": have.get("freeformConfig", "")} ) # copy freeformConfig from have as module is not managing it want_inst_values = {} have_inst_values = {} From cc804e056dc5f58aad0de69b20e72049a9604650 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 6 Dec 2024 19:58:55 -1000 Subject: [PATCH 24/49] dcnm_vrf: Refactor push_to_remote, validate_input 1. push_to_remote() Refactor into - push_diff_create_update() - push_diff_detach() - push_diff_undeploy() - push_diff_delete() - push_diff_create() - push_diff_attach() - push_diff_deploy() 2. validate_input() There were only very small differences between the parameters in attach_spec, lite_spec, and vrf_spec for the different Ansible states. Reduced code duplication by factoring handling for these specs into and moving the Ansible-state conditional into these refactored methods. - attach_spec() - lite_spec() - vrf_spec() --- plugins/modules/dcnm_vrf.py | 1481 ++++++++++++---------- tests/unit/modules/dcnm/test_dcnm_vrf.py | 2 +- 2 files changed, 819 insertions(+), 664 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 7da5504a6..ff163c907 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -647,6 +647,15 @@ def __init__(self, module): self.failed_to_rollback = False self.WAIT_TIME_FOR_DELETE_LOOP = 5 # in seconds + self.vrf_lite_properties = [ + "DOT1Q_ID", + "IP_MASK", + "IPV6_MASK", + "IPV6_NEIGHBOR", + "NEIGHBOR_IP", + "PEER_VRF_NAME", + ] + msg = f"{self.class_name}.__init__(): DONE" self.log.debug(msg) @@ -734,7 +743,8 @@ def compare_properties(dict1, dict2, property_list): def diff_for_attach_deploy(self, want_a, have_a, replace=False): method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" + msg = f"{self.class_name}.{method_name}: ENTERED " + msg += f"with replace == {replace}" self.log.debug(msg) attach_list = [] @@ -792,6 +802,11 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want["extensionValues"] != "" and have["extensionValues"] != "" ): + + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"want[extensionValues] != '' and have[extensionValues] != ''" + self.log.debug(msg) + want_ext_values = want["extensionValues"] want_ext_values = ast.literal_eval(want_ext_values) have_ext_values = have["extensionValues"] @@ -809,14 +824,6 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): # this switch break - vrf_lite_properties = [ - "DOT1Q_ID", - "IP_MASK", - "IPV6_MASK", - "IPV6_NEIGHBOR", - "NEIGHBOR_IP", - "PEER_VRF_NAME", - ] for wlite in want_e["VRF_LITE_CONN"]: for hlite in have_e["VRF_LITE_CONN"]: found = False @@ -826,7 +833,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): found = True interface_match = True if not self.compare_properties( - wlite, hlite, vrf_lite_properties + wlite, hlite, self.vrf_lite_properties ): found = False break @@ -844,16 +851,29 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want["extensionValues"] != "" and have["extensionValues"] == "" ): + + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"want[extensionValues] != '' and have[extensionValues] == ''" + self.log.debug(msg) + found = False elif ( want["extensionValues"] == "" and have["extensionValues"] != "" ): + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"want[extensionValues] == '' and have[extensionValues] != ''" + self.log.debug(msg) + if replace: found = False else: found = True else: + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"in else" + self.log.debug(msg) + found = True want_is_deploy = self.to_bool("is_deploy", want) have_is_deploy = self.to_bool("is_deploy", have) @@ -864,24 +884,46 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if have_is_attached != want_is_attached: if "isAttached" in want: + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"isAttached in want. deleting want[isAttached]" + self.log.debug(msg) del want["isAttached"] want["deployment"] = True attach_list.append(want) if want_is_deploy is True: + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"want_is_deploy is True. " + msg += "deleting want[isAttached] " + msg += "and setting deploy_vrf to True" + self.log.debug(msg) + del want["isAttached"] deploy_vrf = True continue want_deployment = self.to_bool("deployment", want) have_deployment = self.to_bool("deployment", have) + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"want_deployment {want_deployment}, " + msg += f"have_deployment {have_deployment}, " + msg += f"want_is_deploy {want_is_deploy}, " + msg += f"have_is_deploy {have_is_deploy}, " + self.log.debug(msg) if (want_deployment != have_deployment) or ( want_is_deploy != have_is_deploy ): if want_is_deploy is True: + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"setting deploy_vrf = True" + self.log.debug(msg) deploy_vrf = True if self.dict_values_differ(want_inst_values, have_inst_values): + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"dict values differ. Set found = False" + self.log.debug(msg) + found = False if found: @@ -898,7 +940,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if self.to_bool("is_deploy", want): deploy_vrf = True - msg = f"{self.class_name}.{method_name}: " + msg = f"{self.class_name}.{method_name}: Returning " msg += f"deploy_vrf: {deploy_vrf}, " msg += f"attach_list: {json.dumps(attach_list, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -1801,181 +1843,212 @@ def get_diff_replace(self): msg += f"self.diff_deploy: {json.dumps(self.diff_deploy, indent=4)}" self.log.debug(msg) - def get_diff_merge(self, replace=False): + def get_next_vrf_id(self, fabric) -> int: + """ + # Summary + + Return the next available vrf_id for fabric. + + ## Raises + + Calls fail_json() if: + - Controller version is unsupported + - Unable to retrieve next available vrf_id for fabric + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) - # Special cases: - # 1. Auto generate vrfId if its not mentioned by user: - # In this case, we need to query the DCNM to get a usable ID and use it in the payload. - # And also, any such vrf create requests need to be pushed individually(not bulk op). + method = "POST" + attempt = 0 + vrf_id = None + while attempt < 10: + attempt += 1 + path = self.paths["GET_VRF_ID"].format(fabric) + if self.dcnm_version > 11: + vrf_id_obj = dcnm_send(self.module, "GET", path) + else: + vrf_id_obj = dcnm_send(self.module, method, path) + + missing_fabric, not_ok = self.handle_response( + vrf_id_obj, "query_dcnm" + ) + + if missing_fabric or not_ok: + # arobel: TODO: Not covered by UT + msg0 = f"{self.class_name}.{method_name}: " + msg1 = f"{msg0} Fabric {fabric} not present on the controller" + msg2 = f"{msg0} Unable to generate vrfId under fabric {fabric}" + self.module.fail_json(msg=msg1 if missing_fabric else msg2) + + if not vrf_id_obj["DATA"]: + continue + + if self.dcnm_version == 11: + vrf_id = vrf_id_obj["DATA"].get("partitionSegmentId") + elif self.dcnm_version >= 12: + vrf_id = vrf_id_obj["DATA"].get("l3vni") + else: + # arobel: TODO: Not covered by UT + msg = f"{self.class_name}.{method_name}: " + msg += "Unsupported controller version: " + msg += f"{self.dcnm_version}" + self.module.fail_json(msg) + + if vrf_id is None: + msg = f"{self.class_name}.{method_name}: " + msg += "Unable to retrieve vrf_id " + msg += f"for fabric {fabric}" + self.module.fail_json(msg) + return vrf_id + + def diff_merge_create(self, replace=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED with " + msg += f"replace == {replace}" + self.log.debug(msg) + + conf_changed = {} diff_create = [] diff_create_update = [] diff_create_quick = [] - diff_attach = [] - diff_deploy = {} - prev_vrf_id_fetched = None - conf_changed = {} - all_vrfs = "" - - attach_found = False - vrf_found = False for want_c in self.want_create: vrf_found = False for have_c in self.have_create: if want_c["vrfName"] == have_c["vrfName"]: vrf_found = True + msg = f"{self.class_name}.{method_name}: " + msg += f"Calling diff_for_create with: " + msg += f"want_c: {json.dumps(want_c, indent=4, sort_keys=True)}, " + msg += f"have_c: {json.dumps(have_c, indent=4, sort_keys=True)}" + self.log.debug(msg) + diff, conf_chg = self.diff_for_create(want_c, have_c) + + msg = f"{self.class_name}.{method_name}: " + msg += "diff_for_create() returned with: " + msg += f"conf_chg {conf_chg}, " + msg += f"diff {json.dumps(diff, indent=4, sort_keys=True)}, " + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"Updating conf_changed[{want_c['vrfName']}] with {conf_chg}" + self.log.debug(msg) conf_changed.update({want_c["vrfName"]: conf_chg}) + if diff: + msg = f"{self.class_name}.{method_name}: " + msg += f"Appending diff_create_update with " + msg += f"{json.dumps(diff, indent=4, sort_keys=True)}" + self.log.debug(msg) diff_create_update.append(diff) break + if not vrf_found: + msg = f"{self.class_name}.{method_name}: " + msg += f"in if not vrf_found with vrf_found {vrf_found}" + self.log.debug(msg) + vrf_id = want_c.get("vrfId", None) - if vrf_id is None: + if vrf_id is not None: + diff_create.append(want_c) + else: # vrfId is not provided by user. - # Need to query DCNM to fetch next available vrfId and use it here. - method = "POST" - - attempt = 0 - while attempt < 10: - attempt += 1 - path = self.paths["GET_VRF_ID"].format(self.fabric) - if self.dcnm_version > 11: - vrf_id_obj = dcnm_send(self.module, "GET", path) - else: - vrf_id_obj = dcnm_send(self.module, method, path) + # Fetch tje next available vrfId and use it here. + vrf_id = self.get_next_vrf_id(self.fabric) + + want_c.update({"vrfId": vrf_id}) + json_to_dict = json.loads(want_c["vrfTemplateConfig"]) + template_conf = { + "vrfSegmentId": vrf_id, + "vrfName": want_c["vrfName"], + "vrfVlanId": json_to_dict.get("vrfVlanId"), + "vrfVlanName": json_to_dict.get("vrfVlanName"), + "vrfIntfDescription": json_to_dict.get( + "vrfIntfDescription" + ), + "vrfDescription": json_to_dict.get("vrfDescription"), + "mtu": json_to_dict.get("mtu"), + "tag": json_to_dict.get("tag"), + "vrfRouteMap": json_to_dict.get("vrfRouteMap"), + "maxBgpPaths": json_to_dict.get("maxBgpPaths"), + "maxIbgpPaths": json_to_dict.get("maxIbgpPaths"), + "ipv6LinkLocalFlag": json_to_dict.get( + "ipv6LinkLocalFlag" + ), + "trmEnabled": json_to_dict.get("trmEnabled"), + "isRPExternal": json_to_dict.get("isRPExternal"), + "rpAddress": json_to_dict.get("rpAddress"), + "loopbackNumber": json_to_dict.get("loopbackNumber"), + "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup"), + "multicastGroup": json_to_dict.get("multicastGroup"), + "trmBGWMSiteEnabled": json_to_dict.get( + "trmBGWMSiteEnabled" + ), + "advertiseHostRouteFlag": json_to_dict.get( + "advertiseHostRouteFlag" + ), + "advertiseDefaultRouteFlag": json_to_dict.get( + "advertiseDefaultRouteFlag" + ), + "configureStaticDefaultRouteFlag": json_to_dict.get( + "configureStaticDefaultRouteFlag" + ), + "bgpPassword": json_to_dict.get("bgpPassword"), + "bgpPasswordKeyType": json_to_dict.get( + "bgpPasswordKeyType" + ), + } - missing_fabric, not_ok = self.handle_response( - vrf_id_obj, "query_dcnm" + if self.dcnm_version > 11: + template_conf.update( + isRPAbsent=json_to_dict.get("isRPAbsent") ) - - if missing_fabric or not_ok: - # arobel: TODO: Not covered by UT - msg0 = f"{self.class_name}.{method_name}: " - msg1 = f"{msg0} Fabric {self.fabric} not present on the controller" - msg2 = f"{msg0} Unable to generate vrfId for vrf " - msg2 += f"{want_c['vrfName']} under fabric {self.fabric}" - self.module.fail_json(msg=msg1 if missing_fabric else msg2) - - if not vrf_id_obj["DATA"]: - continue - - if self.dcnm_version == 11: - vrf_id = vrf_id_obj["DATA"].get("partitionSegmentId") - elif self.dcnm_version >= 12: - vrf_id = vrf_id_obj["DATA"].get("l3vni") - else: - # arobel: TODO: Not covered by UT - msg = f"{self.class_name}.{method_name}: " - msg += ( - f"Unsupported controller version: {self.dcnm_version}" + template_conf.update( + ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW") + ) + template_conf.update( + NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR") + ) + template_conf.update( + disableRtAuto=json_to_dict.get("disableRtAuto") + ) + template_conf.update( + routeTargetImport=json_to_dict.get( + "routeTargetImport" ) - self.module.fail_json(msg) - - if vrf_id != prev_vrf_id_fetched: - want_c.update({"vrfId": vrf_id}) - json_to_dict = json.loads(want_c["vrfTemplateConfig"]) - template_conf = { - "vrfSegmentId": vrf_id, - "vrfName": want_c["vrfName"], - "vrfVlanId": json_to_dict.get("vrfVlanId"), - "vrfVlanName": json_to_dict.get("vrfVlanName"), - "vrfIntfDescription": json_to_dict.get( - "vrfIntfDescription" - ), - "vrfDescription": json_to_dict.get("vrfDescription"), - "mtu": json_to_dict.get("mtu"), - "tag": json_to_dict.get("tag"), - "vrfRouteMap": json_to_dict.get("vrfRouteMap"), - "maxBgpPaths": json_to_dict.get("maxBgpPaths"), - "maxIbgpPaths": json_to_dict.get("maxIbgpPaths"), - "ipv6LinkLocalFlag": json_to_dict.get( - "ipv6LinkLocalFlag" - ), - "trmEnabled": json_to_dict.get("trmEnabled"), - "isRPExternal": json_to_dict.get("isRPExternal"), - "rpAddress": json_to_dict.get("rpAddress"), - "loopbackNumber": json_to_dict.get("loopbackNumber"), - "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup"), - "multicastGroup": json_to_dict.get("multicastGroup"), - "trmBGWMSiteEnabled": json_to_dict.get( - "trmBGWMSiteEnabled" - ), - "advertiseHostRouteFlag": json_to_dict.get( - "advertiseHostRouteFlag" - ), - "advertiseDefaultRouteFlag": json_to_dict.get( - "advertiseDefaultRouteFlag" - ), - "configureStaticDefaultRouteFlag": json_to_dict.get( - "configureStaticDefaultRouteFlag" - ), - "bgpPassword": json_to_dict.get("bgpPassword"), - "bgpPasswordKeyType": json_to_dict.get( - "bgpPasswordKeyType" - ), - } - - if self.dcnm_version > 11: - template_conf.update( - isRPAbsent=json_to_dict.get("isRPAbsent") - ) - template_conf.update( - ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW") - ) - template_conf.update( - NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR") - ) - template_conf.update( - disableRtAuto=json_to_dict.get("disableRtAuto") - ) - template_conf.update( - routeTargetImport=json_to_dict.get( - "routeTargetImport" - ) - ) - template_conf.update( - routeTargetExport=json_to_dict.get( - "routeTargetExport" - ) - ) - template_conf.update( - routeTargetImportEvpn=json_to_dict.get( - "routeTargetImportEvpn" - ) - ) - template_conf.update( - routeTargetExportEvpn=json_to_dict.get( - "routeTargetExportEvpn" - ) - ) - template_conf.update( - routeTargetImportMvpn=json_to_dict.get( - "routeTargetImportMvpn" - ) - ) - template_conf.update( - routeTargetExportMvpn=json_to_dict.get( - "routeTargetExportMvpn" - ) - ) - - want_c.update( - {"vrfTemplateConfig": json.dumps(template_conf)} + ) + template_conf.update( + routeTargetExport=json_to_dict.get( + "routeTargetExport" ) - prev_vrf_id_fetched = vrf_id - break + ) + template_conf.update( + routeTargetImportEvpn=json_to_dict.get( + "routeTargetImportEvpn" + ) + ) + template_conf.update( + routeTargetExportEvpn=json_to_dict.get( + "routeTargetExportEvpn" + ) + ) + template_conf.update( + routeTargetImportMvpn=json_to_dict.get( + "routeTargetImportMvpn" + ) + ) + template_conf.update( + routeTargetExportMvpn=json_to_dict.get( + "routeTargetExportMvpn" + ) + ) - if not vrf_id: - # arobel: TODO: Not covered by UT - msg = f"{self.class_name}.{method_name}: " - msg += f"Unable to generate vrfId for vrf: {want_c['vrfName']} " - msg += f"under fabric: {self.fabric}" - self.module.fail_json(msg=msg) + want_c.update( + {"vrfTemplateConfig": json.dumps(template_conf)} + ) create_path = self.paths["GET_VRF"].format(self.fabric) @@ -1986,52 +2059,87 @@ def get_diff_merge(self, replace=False): # arobel: TODO: The below is not covered by UT resp = dcnm_send( - self.module, method, create_path, json.dumps(want_c) + self.module, "POST", create_path, json.dumps(want_c) ) self.result["response"].append(resp) fail, self.result["changed"] = self.handle_response(resp, "create") if fail: self.failure(resp) - else: - diff_create.append(want_c) + self.diff_create = diff_create + self.diff_create_update = diff_create_update + self.diff_create_quick = diff_create_quick + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_create: {json.dumps(self.diff_create, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += "self.diff_create_quick: " + msg += f"{json.dumps(self.diff_create_quick, indent=4)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += "self.diff_create_update: " + msg += f"{json.dumps(self.diff_create_update, indent=4)}" + self.log.debug(msg) + + def diff_merge_attach(self, replace=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED with " + msg += f"replace == {replace}" + self.log.debug(msg) + + diff_attach = [] + diff_deploy = {} + conf_changed = {} + + all_vrfs = "" for want_a in self.want_attach: deploy_vrf = "" attach_found = False for have_a in self.have_attach: if want_a["vrfName"] == have_a["vrfName"]: attach_found = True - diff, vrf = self.diff_for_attach_deploy( + diff, deploy_vrf_bool = self.diff_for_attach_deploy( want_a["lanAttachList"], have_a["lanAttachList"], replace ) + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff_for_attach_deploy returned: " + msg += f"deploy_vrf_bool: {deploy_vrf_bool}, " + msg += f"diff: {json.dumps(diff, indent=4, sort_keys=True)} " + self.log.debug(msg) if diff: base = want_a.copy() del base["lanAttachList"] base.update({"lanAttachList": diff}) diff_attach.append(base) - if vrf: + if deploy_vrf_bool is True: deploy_vrf = want_a["vrfName"] else: - if vrf or conf_changed.get(want_a["vrfName"], False): + if deploy_vrf_bool or conf_changed.get(want_a["vrfName"], False): deploy_vrf = want_a["vrfName"] + msg = f"{self.class_name}.{method_name}: " + msg += f"attach_found: {attach_found}" + self.log.debug(msg) + if not attach_found and want_a.get("lanAttachList"): - atch_list = [] + attach_list = [] for attach in want_a["lanAttachList"]: if attach.get("isAttached"): del attach["isAttached"] - atch_list.append(attach) - if atch_list: + attach_list.append(copy.deepcopy(attach)) + if attach_list: base = want_a.copy() del base["lanAttachList"] - base.update({"lanAttachList": atch_list}) + base.update({"lanAttachList": attach_list}) diff_attach.append(base) if attach["is_deploy"]: deploy_vrf = want_a["vrfName"] - for atch in atch_list: + for atch in attach_list: atch["deployment"] = True if deploy_vrf: @@ -2040,33 +2148,33 @@ def get_diff_merge(self, replace=False): if all_vrfs: diff_deploy.update({"vrfNames": all_vrfs[:-1]}) - self.diff_create = diff_create - self.diff_create_update = diff_create_update self.diff_attach = diff_attach self.diff_deploy = diff_deploy - self.diff_create_quick = diff_create_quick msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_create: {json.dumps(self.diff_create, indent=4)}" + msg += "self.diff_attach: " + msg += f"{json.dumps(self.diff_attach, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += ( - f"self.diff_create_update: {json.dumps(self.diff_create_update, indent=4)}" - ) + msg += f"self.diff_deploy: " + msg += f"{json.dumps(self.diff_deploy, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_attach: {json.dumps(self.diff_attach, indent=4)}" + def get_diff_merge(self, replace=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED with " + msg += f"replace == {replace}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_deploy: {json.dumps(self.diff_deploy, indent=4)}" - self.log.debug(msg) + # Special cases: + # 1. Auto generate vrfId if its not mentioned by user: + # In this case, we need to query the DCNM to get a usable ID and use it in the payload. + # And also, any such vrf create requests need to be pushed individually(not bulk op). + + self.diff_merge_create(replace) + self.diff_merge_attach(replace) - msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_create_quick: {json.dumps(self.diff_create_quick, indent=4)}" - self.log.debug(msg) def format_diff(self): method_name = inspect.stack()[0][3] @@ -2384,7 +2492,7 @@ def get_diff_query(self): self.query = query - def push_to_remote(self, is_rollback=False): + def push_diff_create_update(self, is_rollback=False): method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2404,383 +2512,562 @@ def push_to_remote(self, is_rollback=False): return self.failure(resp) - # - # The detach and un-deploy operations are executed before the create,attach and deploy to particularly - # address cases where a VLAN for vrf attachment being deleted is re-used on a new vrf attachment being - # created. This is needed specially for state: overridden - # + def push_diff_detach(self, is_rollback=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) - method = "POST" - if self.diff_detach: - detach_path = path + "/attachments" - - # Update the fabric name to specific fabric to which the switches belong for multisite fabric. - if self.fabric_type == "MFD": - for elem in self.diff_detach: - for node in elem["lanAttachList"]: - node["fabric"] = self.sn_fab[node["serialNumber"]] - - for d_a in self.diff_detach: - for v_a in d_a["lanAttachList"]: - if "is_deploy" in v_a.keys(): - del v_a["is_deploy"] - - resp = dcnm_send( - self.module, method, detach_path, json.dumps(self.diff_detach) - ) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "attach") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) + if not self.diff_detach: + return - method = "POST" - if self.diff_undeploy: - deploy_path = path + "/deployments" - resp = dcnm_send( - self.module, method, deploy_path, json.dumps(self.diff_undeploy) - ) + path = self.paths["GET_VRF"].format(self.fabric) + detach_path = path + "/attachments" + + # Update the fabric name to specific fabric to which the switches belong for multisite fabric. + if self.fabric_type == "MFD": + for elem in self.diff_detach: + for node in elem["lanAttachList"]: + node["fabric"] = self.sn_fab[node["serialNumber"]] + + for d_a in self.diff_detach: + for v_a in d_a["lanAttachList"]: + if "is_deploy" in v_a.keys(): + del v_a["is_deploy"] + + resp = dcnm_send( + self.module, "POST", detach_path, json.dumps(self.diff_detach) + ) + self.result["response"].append(resp) + fail, self.result["changed"] = self.handle_response(resp, "attach") + if fail: + if is_rollback: + self.failed_to_rollback = True + return + self.failure(resp) + + def push_diff_undeploy(self, is_rollback=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) + + if not self.diff_undeploy: + return + + path = self.paths["GET_VRF"].format(self.fabric) + deploy_path = path + "/deployments" + + resp = dcnm_send( + self.module, "POST", deploy_path, json.dumps(self.diff_undeploy) + ) + self.result["response"].append(resp) + fail, self.result["changed"] = self.handle_response(resp, "deploy") + if fail: + if is_rollback: + self.failed_to_rollback = True + return + self.failure(resp) + + def push_diff_delete(self, is_rollback=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) + + if not self.diff_delete: + return + + path = self.paths["GET_VRF"].format(self.fabric) + + del_failure = "" + + self.wait_for_vrf_del_ready() + for vrf, state in self.diff_delete.items(): + if state == "OUT-OF-SYNC": + del_failure += vrf + "," + continue + delete_path = path + "/" + vrf + resp = dcnm_send(self.module, "DELETE", delete_path) self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "deploy") + fail, self.result["changed"] = self.handle_response(resp, "delete") if fail: if is_rollback: self.failed_to_rollback = True return self.failure(resp) - del_failure = "" - - if self.diff_delete and self.wait_for_vrf_del_ready(): - method = "DELETE" - for vrf, state in self.diff_delete.items(): - if state == "OUT-OF-SYNC": - del_failure += vrf + "," - continue - delete_path = path + "/" + vrf - resp = dcnm_send(self.module, method, delete_path) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "delete") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) - if del_failure: msg = f"{self.class_name}.{method_name}: " msg += f"Deletion of vrfs {del_failure[:-1]} has failed" self.result["response"].append(msg) self.module.fail_json(msg=self.result) - method = "POST" - if self.diff_create: + def push_diff_create(self, is_rollback=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) - for vrf in self.diff_create: - json_to_dict = json.loads(vrf["vrfTemplateConfig"]) - vlanId = json_to_dict.get("vrfVlanId", "0") + if not self.diff_create: + return - if vlanId == 0: - vlan_path = self.paths["GET_VLAN"].format(self.fabric) - vlan_data = dcnm_send(self.module, "GET", vlan_path) + path = self.paths["GET_VRF"].format(self.fabric) - if vlan_data["RETURN_CODE"] != 200: - msg = f"{self.class_name}.{method_name}: " - msg += f"Failure getting autogenerated vlan_id {vlan_data}" - self.module.fail_json(msg=msg) + for vrf in self.diff_create: + json_to_dict = json.loads(vrf["vrfTemplateConfig"]) + vlanId = json_to_dict.get("vrfVlanId", "0") - vlanId = vlan_data["DATA"] - - t_conf = { - "vrfSegmentId": vrf["vrfId"], - "vrfName": json_to_dict.get("vrfName", ""), - "vrfVlanId": vlanId, - "vrfVlanName": json_to_dict.get("vrfVlanName"), - "vrfIntfDescription": json_to_dict.get("vrfIntfDescription"), - "vrfDescription": json_to_dict.get("vrfDescription"), - "mtu": json_to_dict.get("mtu"), - "tag": json_to_dict.get("tag"), - "vrfRouteMap": json_to_dict.get("vrfRouteMap"), - "maxBgpPaths": json_to_dict.get("maxBgpPaths"), - "maxIbgpPaths": json_to_dict.get("maxIbgpPaths"), - "ipv6LinkLocalFlag": json_to_dict.get("ipv6LinkLocalFlag"), - "trmEnabled": json_to_dict.get("trmEnabled"), - "isRPExternal": json_to_dict.get("isRPExternal"), - "rpAddress": json_to_dict.get("rpAddress"), - "loopbackNumber": json_to_dict.get("loopbackNumber"), - "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup"), - "multicastGroup": json_to_dict.get("multicastGroup"), - "trmBGWMSiteEnabled": json_to_dict.get("trmBGWMSiteEnabled"), - "advertiseHostRouteFlag": json_to_dict.get( - "advertiseHostRouteFlag" - ), - "advertiseDefaultRouteFlag": json_to_dict.get( - "advertiseDefaultRouteFlag" - ), - "configureStaticDefaultRouteFlag": json_to_dict.get( - "configureStaticDefaultRouteFlag" - ), - "bgpPassword": json_to_dict.get("bgpPassword"), - "bgpPasswordKeyType": json_to_dict.get("bgpPasswordKeyType"), - } + if vlanId == 0: + vlan_path = self.paths["GET_VLAN"].format(self.fabric) + vlan_data = dcnm_send(self.module, "GET", vlan_path) - if self.dcnm_version > 11: - t_conf.update(isRPAbsent=json_to_dict.get("isRPAbsent")) - t_conf.update(ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW")) - t_conf.update(NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR")) - t_conf.update(disableRtAuto=json_to_dict.get("disableRtAuto")) - t_conf.update( - routeTargetImport=json_to_dict.get("routeTargetImport") - ) - t_conf.update( - routeTargetExport=json_to_dict.get("routeTargetExport") - ) - t_conf.update( - routeTargetImportEvpn=json_to_dict.get("routeTargetImportEvpn") - ) - t_conf.update( - routeTargetExportEvpn=json_to_dict.get("routeTargetExportEvpn") - ) - t_conf.update( - routeTargetImportMvpn=json_to_dict.get("routeTargetImportMvpn") - ) - t_conf.update( - routeTargetExportMvpn=json_to_dict.get("routeTargetExportMvpn") - ) + if vlan_data["RETURN_CODE"] != 200: + msg = f"{self.class_name}.{method_name}: " + msg += f"Failure getting autogenerated vlan_id {vlan_data}" + self.module.fail_json(msg=msg) - vrf.update({"vrfTemplateConfig": json.dumps(t_conf)}) + vlanId = vlan_data["DATA"] - resp = dcnm_send(self.module, method, path, json.dumps(vrf)) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "create") - if fail: - if is_rollback: - self.failed_to_rollback = True + t_conf = { + "vrfSegmentId": vrf["vrfId"], + "vrfName": json_to_dict.get("vrfName", ""), + "vrfVlanId": vlanId, + "vrfVlanName": json_to_dict.get("vrfVlanName"), + "vrfIntfDescription": json_to_dict.get("vrfIntfDescription"), + "vrfDescription": json_to_dict.get("vrfDescription"), + "mtu": json_to_dict.get("mtu"), + "tag": json_to_dict.get("tag"), + "vrfRouteMap": json_to_dict.get("vrfRouteMap"), + "maxBgpPaths": json_to_dict.get("maxBgpPaths"), + "maxIbgpPaths": json_to_dict.get("maxIbgpPaths"), + "ipv6LinkLocalFlag": json_to_dict.get("ipv6LinkLocalFlag"), + "trmEnabled": json_to_dict.get("trmEnabled"), + "isRPExternal": json_to_dict.get("isRPExternal"), + "rpAddress": json_to_dict.get("rpAddress"), + "loopbackNumber": json_to_dict.get("loopbackNumber"), + "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup"), + "multicastGroup": json_to_dict.get("multicastGroup"), + "trmBGWMSiteEnabled": json_to_dict.get("trmBGWMSiteEnabled"), + "advertiseHostRouteFlag": json_to_dict.get( + "advertiseHostRouteFlag" + ), + "advertiseDefaultRouteFlag": json_to_dict.get( + "advertiseDefaultRouteFlag" + ), + "configureStaticDefaultRouteFlag": json_to_dict.get( + "configureStaticDefaultRouteFlag" + ), + "bgpPassword": json_to_dict.get("bgpPassword"), + "bgpPasswordKeyType": json_to_dict.get("bgpPasswordKeyType"), + } + + if self.dcnm_version > 11: + t_conf.update(isRPAbsent=json_to_dict.get("isRPAbsent")) + t_conf.update(ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW")) + t_conf.update(NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR")) + t_conf.update(disableRtAuto=json_to_dict.get("disableRtAuto")) + t_conf.update( + routeTargetImport=json_to_dict.get("routeTargetImport") + ) + t_conf.update( + routeTargetExport=json_to_dict.get("routeTargetExport") + ) + t_conf.update( + routeTargetImportEvpn=json_to_dict.get("routeTargetImportEvpn") + ) + t_conf.update( + routeTargetExportEvpn=json_to_dict.get("routeTargetExportEvpn") + ) + t_conf.update( + routeTargetImportMvpn=json_to_dict.get("routeTargetImportMvpn") + ) + t_conf.update( + routeTargetExportMvpn=json_to_dict.get("routeTargetExportMvpn") + ) + + vrf.update({"vrfTemplateConfig": json.dumps(t_conf)}) + + resp = dcnm_send(self.module, "POST", path, json.dumps(vrf)) + self.result["response"].append(resp) + fail, self.result["changed"] = self.handle_response(resp, "create") + if fail: + if is_rollback: + self.failed_to_rollback = True + return + self.failure(resp) + + def push_diff_attach(self, is_rollback=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) + + if not self.diff_attach: + return + + for d_a in self.diff_attach: + for v_a in d_a["lanAttachList"]: + v_a.update(vlan=0) + if "is_deploy" in v_a.keys(): + del v_a["is_deploy"] + if v_a.get("vrf_lite"): + for ip, ser in self.ip_sn.items(): + if ser == v_a["serialNumber"]: + """Before apply the vrf_lite config, need double check if the switch role is started wth Border""" + role = self.inventory_data[ip].get("switchRole") + r = re.search(r"\bborder\b", role.lower()) + if not r: + msg = f"{self.class_name}.{method_name}: " + msg += "VRF LITE cannot be attached to " + msg += f"switch {ip} with role {role}" + self.module.fail_json(msg=msg) + + lite_objects = self.get_vrf_lite_objects(v_a) + if not lite_objects.get("DATA"): + msg = f"{self.class_name}.{method_name}: " + msg += f"Early return, no lite objects." return - self.failure(resp) - if self.diff_attach: - for d_a in self.diff_attach: - for v_a in d_a["lanAttachList"]: - v_a.update(vlan=0) - if "is_deploy" in v_a.keys(): - del v_a["is_deploy"] - if v_a.get("vrf_lite"): - for ip, ser in self.ip_sn.items(): - if ser == v_a["serialNumber"]: - """Before apply the vrf_lite config, need double check if the switch role is started wth Border""" - role = self.inventory_data[ip].get("switchRole") - r = re.search(r"\bborder\b", role.lower()) - if not r: - msg = f"{self.class_name}.{method_name}: " - msg += "VRF LITE cannot be attached to " - msg += f"switch {ip} with role {role}" - self.module.fail_json(msg=msg) - - lite_objects = self.get_vrf_lite_objects(v_a) - if not lite_objects.get("DATA"): - return + lite = lite_objects["DATA"][0]["switchDetailsList"][0][ + "extensionPrototypeValues" + ] + msg = f"ZZZ: {self.class_name}.{method_name}: " + msg += f"lite: {json.dumps(lite, indent=4, sort_keys=True)}" + self.log.debug(msg) - lite = lite_objects["DATA"][0]["switchDetailsList"][0][ - "extensionPrototypeValues" - ] - ext_values = None - extension_values = {} - extension_values["VRF_LITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = [] + ext_values = None + extension_values = {} + extension_values["VRF_LITE_CONN"] = [] + extension_values["MULTISITE_CONN"] = [] - for ext_l in lite: - if str(ext_l.get("extensionType")) != "VRF_LITE": - continue - ext_values = ext_l["extensionValues"] - ext_values = ast.literal_eval(ext_values) - for ad_l in v_a.get("vrf_lite"): - if ad_l["interface"] == ext_values["IF_NAME"]: - nbr_dict = {} - nbr_dict["IF_NAME"] = ad_l["interface"] - - if ad_l["dot1q"]: - nbr_dict["DOT1Q_ID"] = str(ad_l["dot1q"]) - else: - nbr_dict["DOT1Q_ID"] = str( - ext_values["DOT1Q_ID"] - ) - - if ad_l["ipv4_addr"]: - nbr_dict["IP_MASK"] = ad_l["ipv4_addr"] - else: - nbr_dict["IP_MASK"] = ext_values["IP_MASK"] - - if ad_l["neighbor_ipv4"]: - nbr_dict["NEIGHBOR_IP"] = ad_l["neighbor_ipv4"] - else: - nbr_dict["NEIGHBOR_IP"] = ext_values[ - "NEIGHBOR_IP" - ] + for ext_l in lite: + if str(ext_l.get("extensionType")) != "VRF_LITE": + continue + ext_values = ext_l["extensionValues"] + ext_values = ast.literal_eval(ext_values) + for item in v_a.get("vrf_lite"): + if item["interface"] == ext_values["IF_NAME"]: + nbr_dict = {} + nbr_dict["IF_NAME"] = item["interface"] + + if item["dot1q"]: + nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) + else: + nbr_dict["DOT1Q_ID"] = str( + ext_values["DOT1Q_ID"] + ) + + if item["ipv4_addr"]: + nbr_dict["IP_MASK"] = item["ipv4_addr"] + else: + nbr_dict["IP_MASK"] = ext_values["IP_MASK"] - nbr_dict["NEIGHBOR_ASN"] = ext_values[ - "NEIGHBOR_ASN" + if item["neighbor_ipv4"]: + nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] + else: + nbr_dict["NEIGHBOR_IP"] = ext_values[ + "NEIGHBOR_IP" ] - if ad_l["ipv6_addr"]: - nbr_dict["IPV6_MASK"] = ad_l["ipv6_addr"] - else: - nbr_dict["IPV6_MASK"] = ext_values["IPV6_MASK"] + nbr_dict["NEIGHBOR_ASN"] = ext_values[ + "NEIGHBOR_ASN" + ] - if ad_l["neighbor_ipv6"]: - nbr_dict["IPV6_NEIGHBOR"] = ad_l[ - "neighbor_ipv6" - ] - else: - nbr_dict["IPV6_NEIGHBOR"] = ext_values[ - "IPV6_NEIGHBOR" - ] + if item["ipv6_addr"]: + nbr_dict["IPV6_MASK"] = item["ipv6_addr"] + else: + nbr_dict["IPV6_MASK"] = ext_values["IPV6_MASK"] - nbr_dict["AUTO_VRF_LITE_FLAG"] = ext_values[ - "AUTO_VRF_LITE_FLAG" + if item["neighbor_ipv6"]: + nbr_dict["IPV6_NEIGHBOR"] = item[ + "neighbor_ipv6" + ] + else: + nbr_dict["IPV6_NEIGHBOR"] = ext_values[ + "IPV6_NEIGHBOR" ] - if ad_l["peer_vrf"]: - nbr_dict["PEER_VRF_NAME"] = ad_l["peer_vrf"] - else: - nbr_dict["PEER_VRF_NAME"] = ext_values[ - "PEER_VRF_NAME" - ] + nbr_dict["AUTO_VRF_LITE_FLAG"] = ext_values[ + "AUTO_VRF_LITE_FLAG" + ] - nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = ( - "Ext_VRF_Lite_Jython" - ) - vrflite_con = {} - vrflite_con["VRF_LITE_CONN"] = [] - vrflite_con["VRF_LITE_CONN"].append( - copy.deepcopy(nbr_dict) - ) - if extension_values["VRF_LITE_CONN"]: - extension_values["VRF_LITE_CONN"][ - "VRF_LITE_CONN" - ].extend(vrflite_con["VRF_LITE_CONN"]) - else: - extension_values["VRF_LITE_CONN"] = vrflite_con - - ms_con = {} - ms_con["MULTISITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = json.dumps( - ms_con - ) + if item["peer_vrf"]: + nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] + else: + nbr_dict["PEER_VRF_NAME"] = ext_values[ + "PEER_VRF_NAME" + ] - del ad_l + nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = ( + "Ext_VRF_Lite_Jython" + ) + vrflite_con = {} + vrflite_con["VRF_LITE_CONN"] = [] + vrflite_con["VRF_LITE_CONN"].append( + copy.deepcopy(nbr_dict) + ) + if extension_values["VRF_LITE_CONN"]: + extension_values["VRF_LITE_CONN"][ + "VRF_LITE_CONN" + ].extend(vrflite_con["VRF_LITE_CONN"]) + else: + extension_values["VRF_LITE_CONN"] = vrflite_con + + ms_con = {} + ms_con["MULTISITE_CONN"] = [] + extension_values["MULTISITE_CONN"] = json.dumps( + ms_con + ) - if ext_values is None: - for ip, ser in self.ip_sn.items(): - # arobel TODO: Not covered by UT - if ser == v_a["serialNumber"]: - msg = f"{self.class_name}.{method_name}: " - msg += "No VRF LITE capable interfaces found " - msg += f"on this switch {ip}" - self.module.fail_json(msg=msg) - else: - extension_values["VRF_LITE_CONN"] = json.dumps( - extension_values["VRF_LITE_CONN"] - ) - v_a["extensionValues"] = json.dumps( - extension_values - ).replace(" ", "") - if v_a.get("vrf_lite", None) is not None: - del v_a["vrf_lite"] + del item + if ext_values is None: + for ip, ser in self.ip_sn.items(): + # arobel TODO: Not covered by UT + if ser == v_a["serialNumber"]: + msg = f"{self.class_name}.{method_name}: " + msg += "No VRF LITE capable interfaces found " + msg += f"on this switch {ip}" + self.module.fail_json(msg=msg) else: - if "vrf_lite" in v_a.keys(): + extension_values["VRF_LITE_CONN"] = json.dumps( + extension_values["VRF_LITE_CONN"] + ) + v_a["extensionValues"] = json.dumps( + extension_values + ).replace(" ", "") + if v_a.get("vrf_lite", None) is not None: del v_a["vrf_lite"] - path = self.paths["GET_VRF"].format(self.fabric) - method = "POST" - attach_path = path + "/attachments" - - # Update the fabric name to specific fabric to which the switches belong for multisite fabric. - if self.fabric_type == "MFD": - for elem in self.diff_attach: - for node in elem["lanAttachList"]: - node["fabric"] = self.sn_fab[node["serialNumber"]] - resp = dcnm_send( - self.module, method, attach_path, json.dumps(self.diff_attach) - ) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "attach") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) + else: + if "vrf_lite" in v_a.keys(): + del v_a["vrf_lite"] - method = "POST" - if self.diff_deploy: - deploy_path = path + "/deployments" - resp = dcnm_send( - self.module, method, deploy_path, json.dumps(self.diff_deploy) - ) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "deploy") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) + path = self.paths["GET_VRF"].format(self.fabric) + attach_path = path + "/attachments" + + # For multisite fabrics, update the fabric name to the child fabric + # containing the switches. + if self.fabric_type == "MFD": + for elem in self.diff_attach: + for node in elem["lanAttachList"]: + node["fabric"] = self.sn_fab[node["serialNumber"]] + resp = dcnm_send( + self.module, "POST", attach_path, json.dumps(self.diff_attach) + ) + self.result["response"].append(resp) + fail, self.result["changed"] = self.handle_response(resp, "attach") + if fail: + if is_rollback: + self.failed_to_rollback = True + return + self.failure(resp) + + def push_diff_deploy(self, is_rollback=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) + + if not self.diff_deploy: + return + + path = self.paths["GET_VRF"].format(self.fabric) + deploy_path = path + "/deployments" + resp = dcnm_send( + self.module, "POST", deploy_path, json.dumps(self.diff_deploy) + ) + self.result["response"].append(resp) + fail, self.result["changed"] = self.handle_response(resp, "deploy") + if fail: + if is_rollback: + self.failed_to_rollback = True + return + self.failure(resp) + + def push_to_remote(self, is_rollback=False): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: ENTERED" + self.log.debug(msg) + + self.push_diff_create_update(is_rollback) + + # The detach and un-deploy operations are executed before the + # create,attach and deploy to address cases where a VLAN for vrf + # attachment being deleted is re-used on a new vrf attachment being + # created. This is needed specially for state: overridden + + self.push_diff_detach(is_rollback) + self.push_diff_undeploy(is_rollback) + + self.push_diff_delete(is_rollback) + self.push_diff_create(is_rollback) + self.push_diff_attach(is_rollback) + self.push_diff_deploy(is_rollback) def wait_for_vrf_del_ready(self): + """ + # Summary + + Wait for VRFs to be ready for deletion. + + ## Raises + + Calls fail_json if VRF has associated network attachments. + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) - method = "GET" - if self.diff_delete: - for vrf in self.diff_delete: - state = False - path = self.paths["GET_VRF_ATTACH"].format(self.fabric, vrf) - while not state: - resp = dcnm_send(self.module, method, path) - state = True - if resp.get("DATA") is not None: - attach_list = resp["DATA"][0]["lanAttachList"] + + for vrf in self.diff_delete: + ok_to_delete = False + path = self.paths["GET_VRF_ATTACH"].format(self.fabric, vrf) + + while not ok_to_delete: + resp = dcnm_send(self.module, "GET", path) + ok_to_delete = True + if resp.get("DATA") is None: + time.sleep(self.WAIT_TIME_FOR_DELETE_LOOP) + continue + + attach_list = resp["DATA"][0]["lanAttachList"] + msg = f"{self.class_name}.{method_name}: " + msg += f"ok_to_delete: {ok_to_delete}, " + msg += f"attach_list: {json.dumps(attach_list, indent=4)}" + self.log.debug(msg) + + for attach in attach_list: + if ( + attach["lanAttachState"] == "OUT-OF-SYNC" + or attach["lanAttachState"] == "FAILED" + ): + self.diff_delete.update({vrf: "OUT-OF-SYNC"}) + break + if ( + attach["lanAttachState"] == "DEPLOYED" + and attach["isLanAttached"] is True + ): + vrf_name = attach.get("vrfName", "unknown") + fabric_name = attach.get("fabricName", "unknown") + switch_ip = attach.get("ipAddress", "unknown") + switch_name = attach.get("switchName", "unknown") + vlan_id = attach.get("vlanId", "unknown") msg = f"{self.class_name}.{method_name}: " - msg += f"attach_list: {json.dumps(attach_list, indent=4)}" - self.log.debug(msg) + msg += f"Network attachments associated with vrf {vrf_name} " + msg += "must be removed (e.g. using the dcnm_network module) " + msg += "prior to deleting the vrf. " + msg += f"Details: fabric_name: {fabric_name}, " + msg += f"vrf_name: {vrf_name}. " + msg += "Network attachments found on " + msg += f"switch_ip: {switch_ip}, " + msg += f"switch_name: {switch_name}, " + msg += f"vlan_id: {vlan_id}" + self.module.fail_json(msg=msg) + if attach["lanAttachState"] != "NA": + time.sleep(self.WAIT_TIME_FOR_DELETE_LOOP) + self.diff_delete.update({vrf: "DEPLOYED"}) + ok_to_delete = False + break + self.diff_delete.update({vrf: "NA"}) - for atch in attach_list: - if ( - atch["lanAttachState"] == "OUT-OF-SYNC" - or atch["lanAttachState"] == "FAILED" - ): - self.diff_delete.update({vrf: "OUT-OF-SYNC"}) - break - if ( - atch["lanAttachState"] == "DEPLOYED" - and atch["isLanAttached"] is True - ): - vrf_name = atch.get("vrfName", "unknown") - fabric_name = atch.get("fabricName", "unknown") - switch_ip = atch.get("ipAddress", "unknown") - switch_name = atch.get("switchName", "unknown") - vlan_id = atch.get("vlanId", "unknown") - msg = f"{self.class_name}.{method_name}: " - msg += f"Network attachments associated with vrf {vrf_name} " - msg += "must be removed (e.g. using the dcnm_network module) " - msg += "prior to deleting the vrf. " - msg += f"Details: fabric_name: {fabric_name}, " - msg += f"vrf_name: {vrf_name}. " - msg += "Network attachments found on " - msg += f"switch_ip: {switch_ip}, " - msg += f"switch_name: {switch_name}, " - msg += f"vlan_id: {vlan_id}" - self.module.fail_json(msg=msg) - if atch["lanAttachState"] != "NA": - time.sleep(self.WAIT_TIME_FOR_DELETE_LOOP) - self.diff_delete.update({vrf: "DEPLOYED"}) - state = False - break - self.diff_delete.update({vrf: "NA"}) + def attach_spec(self): + spec = {} + spec["deploy"] = {"default": True, "type": "bool"} + spec["ip_address"] = {"required": True, "type": "str"} + spec["import_evpn_rt"] = {"default": "", "type": "str"} + spec["export_evpn_rt"] = {"default": "", "type": "str"} - return True + if self.params["state"] in ("merged", "overridden", "replaced"): + spec["vrf_lite"] = {"type": "list"} + else: + spec["vrf_lite"] = {"default": [], "type": "list"} + + return copy.deepcopy(spec) + + def lite_spec(self): + spec = {} + spec["dot1q"] = {"type": "int"} + spec["ipv4_addr"] = {"type": "ipv4_subnet"} + spec["ipv6_addr"] = {"type": "ipv6"} + spec["neighbor_ipv4"] = {"type": "ipv4"} + spec["neighbor_ipv6"] = {"type": "ipv6"} + spec["peer_vrf"] = {"type": "str"} + + if self.params["state"] in ("merged", "overridden", "replaced"): + spec["interface"] = {"required": True, "type": "str"} + else: + spec["interface"] = {"type": "str"} + + return copy.deepcopy(spec) + + def vrf_spec(self): + spec = {} + spec["adv_default_routes"] = {"default": True, "type": "bool"} + spec["adv_host_routes"] = {"default": False, "type": "bool"} + + spec["attach"] = {"type": "list"} + spec["bgp_password"] = {"default": "", "type": "str"} + spec["bgp_passwd_encrypt"] = {"choices": [3, 7], "default": 3, "type": "int"} + spec["disable_rt_auto"] = {"default": False, "type": "bool"} + + spec["export_evpn_rt"] = {"default": "", "type": "str"} + spec["export_mvpn_rt"] = {"default": "", "type": "str"} + spec["export_vpn_rt"] = {"default": "", "type": "str"} + + spec["import_evpn_rt"] = {"default": "", "type": "str"} + spec["import_mvpn_rt"] = {"default": "", "type": "str"} + spec["import_vpn_rt"] = {"default": "", "type": "str"} + + spec["ipv6_linklocal_enable"] = {"default": True, "type": "bool"} + + spec["loopback_route_tag"] = {"default": 12345, "range_max": 4294967295, "type": "int"} + spec["max_bgp_paths"] = {"default": 1, "range_max": 64, "range_min": 1, "type": "int"} + spec["max_ibgp_paths"] = {"default": 2, "range_max": 64, "range_min": 1, "type": "int"} + spec["netflow_enable"] = {"default": False, "type": "bool"} + spec["nf_monitor"] = {"default": "", "type": "str"} + + spec["no_rp"] = {"default": False, "type": "bool"} + spec["overlay_mcast_group"] = {"default": "", "type": "str"} + + + spec["redist_direct_rmap"] = {"default": "FABRIC-RMAP-REDIST-SUBNET", "type": "str"} + spec["rp_address"] = {"default": "", "type": "str"} + + + spec["rp_external"] = {"default": False, "type": "bool"} + spec["rp_loopback_id"] = {"default": "", "range_max": 1023, "type": "int"} + + spec["service_vrf_template"] = {"default": None, "type": "str"} + + spec["source"] = {"default": None, "type": "str"} + spec["static_default_route"] = {"default": True, "type": "bool"} + + + spec["trm_bgw_msite"] = {"default": False, "type": "bool"} + + spec["trm_enable"] = {"default": False, "type": "bool"} + + + spec["underlay_mcast_ip"] = {"default": "", "type": "str"} + + + spec["vlan_id"] = {"range_max": 4094, "type": "int"} + spec["vrf_description"] = {"default": "", "type": "str"} + + spec["vrf_id"] = {"range_max": 16777214, "type": "int"} + spec["vrf_intf_desc"] = {"default": "", "type": "str"} + + spec["vrf_int_mtu"] = {"default": 9216, "range_max": 9216, "range_min": 68, "type": "int"} + + spec["vrf_name"] = {"length_max": 32, "required": True, "type": "str"} + spec["vrf_template"] = {"default": "Default_VRF_Universal", "type": "str"} + spec["vrf_extension_template"] = {"default": "Default_VRF_Extension_Universal", "type": "str"} + + spec["vrf_vlan_name"] = {"default": "", "type": "str"} + + + if self.params["state"] in ("merged", "overridden", "replaced"): + spec["deploy"] = {"default": True, "type": "bool"} + else: + spec["deploy"] = {"type": "bool"} + + return copy.deepcopy(spec) def validate_input(self): """Parse the playbook values, validate to param specs.""" @@ -2790,75 +3077,11 @@ def validate_input(self): state = self.params["state"] - if state in ("merged", "overridden", "replaced"): - - vrf_spec = dict( - vrf_name=dict(required=True, type="str", length_max=32), - vrf_id=dict(type="int", range_max=16777214), - vrf_template=dict(type="str", default="Default_VRF_Universal"), - vrf_extension_template=dict( - type="str", default="Default_VRF_Extension_Universal" - ), - vlan_id=dict(type="int", range_max=4094), - source=dict(type="str", default=None), - service_vrf_template=dict(type="str", default=None), - attach=dict(type="list"), - deploy=dict(type="bool", default=True), - vrf_vlan_name=dict(type="str", default=""), - vrf_intf_desc=dict(type="str", default=""), - vrf_description=dict(type="str", default=""), - vrf_int_mtu=dict( - type="int", range_min=68, range_max=9216, default=9216 - ), - loopback_route_tag=dict( - type="int", default=12345, range_max=4294967295 - ), - redist_direct_rmap=dict( - type="str", default="FABRIC-RMAP-REDIST-SUBNET" - ), - max_bgp_paths=dict(type="int", range_min=1, range_max=64, default=1), - max_ibgp_paths=dict(type="int", range_min=1, range_max=64, default=2), - ipv6_linklocal_enable=dict(type="bool", default=True), - trm_enable=dict(type="bool", default=False), - no_rp=dict(type="bool", default=False), - rp_external=dict(type="bool", default=False), - rp_address=dict(type="str", default=""), - rp_loopback_id=dict(type="int", range_max=1023, default=""), - underlay_mcast_ip=dict(type="str", default=""), - overlay_mcast_group=dict(type="str", default=""), - trm_bgw_msite=dict(type="bool", default=False), - adv_host_routes=dict(type="bool", default=False), - adv_default_routes=dict(type="bool", default=True), - static_default_route=dict(type="bool", default=True), - bgp_password=dict(type="str", default=""), - bgp_passwd_encrypt=dict(type="int", default=3, choices=[3, 7]), - netflow_enable=dict(type="bool", default=False), - nf_monitor=dict(type="str", default=""), - disable_rt_auto=dict(type="bool", default=False), - import_vpn_rt=dict(type="str", default=""), - export_vpn_rt=dict(type="str", default=""), - import_evpn_rt=dict(type="str", default=""), - export_evpn_rt=dict(type="str", default=""), - import_mvpn_rt=dict(type="str", default=""), - export_mvpn_rt=dict(type="str", default=""), - ) - att_spec = dict( - ip_address=dict(required=True, type="str"), - deploy=dict(type="bool", default=True), - vrf_lite=dict(type="list"), - import_evpn_rt=dict(type="str", default=""), - export_evpn_rt=dict(type="str", default=""), - ) - lite_spec = dict( - interface=dict(required=True, type="str"), - peer_vrf=dict(type="str"), - ipv4_addr=dict(type="ipv4_subnet"), - neighbor_ipv4=dict(type="ipv4"), - ipv6_addr=dict(type="ipv6"), - neighbor_ipv6=dict(type="ipv6"), - dot1q=dict(type="int"), - ) + attach_spec = self.attach_spec() + lite_spec = self.lite_spec() + vrf_spec = self.vrf_spec() + if state in ("merged", "overridden", "replaced"): msg = None if self.config: for vrf in self.config: @@ -2871,10 +3094,8 @@ def validate_input(self): if "vrf_name" not in vrf: msg = "vrf_name is mandatory under vrf parameters" - if "attach" in vrf and vrf["attach"]: + if isinstance(vrf.get("attach"), list): for attach in vrf["attach"]: - # if 'ip_address' not in attach or 'vlan_id' not in attach: - # msg = "ip_address and vlan_id are mandatory under attach parameters" if "ip_address" not in attach: msg = "ip_address is mandatory under attach parameters" else: @@ -2894,7 +3115,7 @@ def validate_input(self): for entry in vrf.get("attach"): entry["deploy"] = vrf["deploy"] valid_att, invalid_att = validate_list_of_dicts( - vrf["attach"], att_spec + vrf["attach"], attach_spec ) vrf["attach"] = valid_att @@ -2909,80 +3130,14 @@ def validate_input(self): self.validated.append(vrf) if invalid_params: - msg = "Invalid parameters in playbook: {0}".format( - "\n".join(invalid_params) - ) + # arobel: TODO: Not in UT + msg = f"{self.class_name}.{method_name}: " + msg += "Invalid parameters in playbook: " + msg += f"{','.join(invalid_params)}" self.module.fail_json(msg=msg) else: - vrf_spec = dict( - vrf_name=dict(required=True, type="str", length_max=32), - vrf_id=dict(type="int", range_max=16777214), - vrf_template=dict(type="str", default="Default_VRF_Universal"), - vrf_extension_template=dict( - type="str", default="Default_VRF_Extension_Universal" - ), - vlan_id=dict(type="int", range_max=4094), - source=dict(type="str", default=None), - service_vrf_template=dict(type="str", default=None), - attach=dict(type="list"), - deploy=dict(type="bool"), - vrf_vlan_name=dict(type="str", default=""), - vrf_intf_desc=dict(type="str", default=""), - vrf_description=dict(type="str", default=""), - vrf_int_mtu=dict( - type="int", range_min=68, range_max=9216, default=9216 - ), - loopback_route_tag=dict( - type="int", default=12345, range_max=4294967295 - ), - redist_direct_rmap=dict( - type="str", default="FABRIC-RMAP-REDIST-SUBNET" - ), - max_bgp_paths=dict(type="int", range_min=1, range_max=64, default=1), - max_ibgp_paths=dict(type="int", range_min=1, range_max=64, default=2), - ipv6_linklocal_enable=dict(type="bool", default=True), - trm_enable=dict(type="bool", default=False), - no_rp=dict(type="bool", default=False), - rp_external=dict(type="bool", default=False), - rp_address=dict(type="str", default=""), - rp_loopback_id=dict(type="int", range_max=1023, default=""), - underlay_mcast_ip=dict(type="str", default=""), - overlay_mcast_group=dict(type="str", default=""), - trm_bgw_msite=dict(type="bool", default=False), - adv_host_routes=dict(type="bool", default=False), - adv_default_routes=dict(type="bool", default=True), - static_default_route=dict(type="bool", default=True), - bgp_password=dict(type="str", default=""), - bgp_passwd_encrypt=dict(type="int", default=3, choices=[3, 7]), - netflow_enable=dict(type="bool", default=False), - nf_monitor=dict(type="str", default=""), - disable_rt_auto=dict(type="bool", default=False), - import_vpn_rt=dict(type="str", default=""), - export_vpn_rt=dict(type="str", default=""), - import_evpn_rt=dict(type="str", default=""), - export_evpn_rt=dict(type="str", default=""), - import_mvpn_rt=dict(type="str", default=""), - export_mvpn_rt=dict(type="str", default=""), - ) - att_spec = dict( - ip_address=dict(required=True, type="str"), - deploy=dict(type="bool", default=True), - vrf_lite=dict(type="list", default=[]), - import_evpn_rt=dict(type="str", default=""), - export_evpn_rt=dict(type="str", default=""), - ) - lite_spec = dict( - interface=dict(type="str"), - peer_vrf=dict(type="str"), - ipv4_addr=dict(type="ipv4_subnet"), - neighbor_ipv4=dict(type="ipv4"), - ipv6_addr=dict(type="ipv6"), - neighbor_ipv6=dict(type="ipv6"), - dot1q=dict(type="int"), - ) - if self.config: valid_vrf, invalid_params = validate_list_of_dicts( self.config, vrf_spec @@ -2990,7 +3145,7 @@ def validate_input(self): for vrf in valid_vrf: if vrf.get("attach"): valid_att, invalid_att = validate_list_of_dicts( - vrf["attach"], att_spec + vrf["attach"], attach_spec ) vrf["attach"] = valid_att invalid_params.extend(invalid_att) @@ -3004,9 +3159,10 @@ def validate_input(self): self.validated.append(vrf) if invalid_params: - msg = "Invalid parameters in playbook: {0}".format( - "\n".join(invalid_params) - ) + # arobel: TODO: Not in UT + msg = f"{self.class_name}.{method_name}: " + msg += "Invalid parameters in playbook: " + msg += f"{','.join(invalid_params)}" self.module.fail_json(msg=msg) def handle_response(self, res, op): @@ -3018,9 +3174,8 @@ def handle_response(self, res, op): changed = True if op == "query_dcnm": - # This if blocks handles responses to the query APIs against DCNM. + # These if blocks handle responses to the query APIs. # Basically all GET operations. - # if res.get("ERROR") == "Not Found" and res["RETURN_CODE"] == 404: return True, False if res["RETURN_CODE"] != 200 or res["MESSAGE"] != "OK": @@ -3050,8 +3205,8 @@ def failure(self, resp): self.module.fail_json(msg=resp) return - # Implementing a per task rollback logic here so that we rollback DCNM to the have state - # whenever there is a failure in any of the APIs. + # Implementing a per task rollback logic here so that we rollback + # to the have state whenever there is a failure in any of the APIs. # The idea would be to run overridden state with want=have and have=dcnm_state self.want_create = self.have_create self.want_attach = self.have_attach diff --git a/tests/unit/modules/dcnm/test_dcnm_vrf.py b/tests/unit/modules/dcnm/test_dcnm_vrf.py index 290b285db..ee60ea9e1 100644 --- a/tests/unit/modules/dcnm/test_dcnm_vrf.py +++ b/tests/unit/modules/dcnm/test_dcnm_vrf.py @@ -1144,7 +1144,7 @@ def test_dcnm_vrf_delete_failure(self): dict(state="deleted", fabric="test_fabric", config=self.playbook_config) ) result = self.execute_module(changed=False, failed=True) - msg = "DcnmVrf.push_to_remote: Deletion of vrfs test_vrf_1 has failed" + msg = "DcnmVrf.push_diff_delete: Deletion of vrfs test_vrf_1 has failed" self.assertEqual(result["msg"]["response"][2], msg) def test_dcnm_vrf_query(self): From 78f9e0d5d329866de3d6a72109020bdfd1b1c16b Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 6 Dec 2024 20:07:54 -1000 Subject: [PATCH 25/49] appease linters --- plugins/modules/dcnm_vrf.py | 146 ++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 83 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index ff163c907..e045e0f38 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -885,7 +885,9 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if "isAttached" in want: msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"isAttached in want. deleting want[isAttached]" + msg += ( + f"isAttached in want. deleting want[isAttached]" + ) self.log.debug(msg) del want["isAttached"] @@ -1870,9 +1872,7 @@ def get_next_vrf_id(self, fabric) -> int: else: vrf_id_obj = dcnm_send(self.module, method, path) - missing_fabric, not_ok = self.handle_response( - vrf_id_obj, "query_dcnm" - ) + missing_fabric, not_ok = self.handle_response(vrf_id_obj, "query_dcnm") if missing_fabric or not_ok: # arobel: TODO: Not covered by UT @@ -1966,27 +1966,21 @@ def diff_merge_create(self, replace=False): "vrfName": want_c["vrfName"], "vrfVlanId": json_to_dict.get("vrfVlanId"), "vrfVlanName": json_to_dict.get("vrfVlanName"), - "vrfIntfDescription": json_to_dict.get( - "vrfIntfDescription" - ), + "vrfIntfDescription": json_to_dict.get("vrfIntfDescription"), "vrfDescription": json_to_dict.get("vrfDescription"), "mtu": json_to_dict.get("mtu"), "tag": json_to_dict.get("tag"), "vrfRouteMap": json_to_dict.get("vrfRouteMap"), "maxBgpPaths": json_to_dict.get("maxBgpPaths"), "maxIbgpPaths": json_to_dict.get("maxIbgpPaths"), - "ipv6LinkLocalFlag": json_to_dict.get( - "ipv6LinkLocalFlag" - ), + "ipv6LinkLocalFlag": json_to_dict.get("ipv6LinkLocalFlag"), "trmEnabled": json_to_dict.get("trmEnabled"), "isRPExternal": json_to_dict.get("isRPExternal"), "rpAddress": json_to_dict.get("rpAddress"), "loopbackNumber": json_to_dict.get("loopbackNumber"), "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup"), "multicastGroup": json_to_dict.get("multicastGroup"), - "trmBGWMSiteEnabled": json_to_dict.get( - "trmBGWMSiteEnabled" - ), + "trmBGWMSiteEnabled": json_to_dict.get("trmBGWMSiteEnabled"), "advertiseHostRouteFlag": json_to_dict.get( "advertiseHostRouteFlag" ), @@ -1997,15 +1991,11 @@ def diff_merge_create(self, replace=False): "configureStaticDefaultRouteFlag" ), "bgpPassword": json_to_dict.get("bgpPassword"), - "bgpPasswordKeyType": json_to_dict.get( - "bgpPasswordKeyType" - ), + "bgpPasswordKeyType": json_to_dict.get("bgpPasswordKeyType"), } if self.dcnm_version > 11: - template_conf.update( - isRPAbsent=json_to_dict.get("isRPAbsent") - ) + template_conf.update(isRPAbsent=json_to_dict.get("isRPAbsent")) template_conf.update( ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW") ) @@ -2016,14 +2006,10 @@ def diff_merge_create(self, replace=False): disableRtAuto=json_to_dict.get("disableRtAuto") ) template_conf.update( - routeTargetImport=json_to_dict.get( - "routeTargetImport" - ) + routeTargetImport=json_to_dict.get("routeTargetImport") ) template_conf.update( - routeTargetExport=json_to_dict.get( - "routeTargetExport" - ) + routeTargetExport=json_to_dict.get("routeTargetExport") ) template_conf.update( routeTargetImportEvpn=json_to_dict.get( @@ -2046,9 +2032,7 @@ def diff_merge_create(self, replace=False): ) ) - want_c.update( - {"vrfTemplateConfig": json.dumps(template_conf)} - ) + want_c.update({"vrfTemplateConfig": json.dumps(template_conf)}) create_path = self.paths["GET_VRF"].format(self.fabric) @@ -2118,7 +2102,9 @@ def diff_merge_attach(self, replace=False): if deploy_vrf_bool is True: deploy_vrf = want_a["vrfName"] else: - if deploy_vrf_bool or conf_changed.get(want_a["vrfName"], False): + if deploy_vrf_bool or conf_changed.get( + want_a["vrfName"], False + ): deploy_vrf = want_a["vrfName"] msg = f"{self.class_name}.{method_name}: " @@ -2175,7 +2161,6 @@ def get_diff_merge(self, replace=False): self.diff_merge_create(replace) self.diff_merge_attach(replace) - def format_diff(self): method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" @@ -2534,9 +2519,7 @@ def push_diff_detach(self, is_rollback=False): if "is_deploy" in v_a.keys(): del v_a["is_deploy"] - resp = dcnm_send( - self.module, "POST", detach_path, json.dumps(self.diff_detach) - ) + resp = dcnm_send(self.module, "POST", detach_path, json.dumps(self.diff_detach)) self.result["response"].append(resp) fail, self.result["changed"] = self.handle_response(resp, "attach") if fail: @@ -2645,9 +2628,7 @@ def push_diff_create(self, is_rollback=False): "L3VniMcastGroup": json_to_dict.get("L3VniMcastGroup"), "multicastGroup": json_to_dict.get("multicastGroup"), "trmBGWMSiteEnabled": json_to_dict.get("trmBGWMSiteEnabled"), - "advertiseHostRouteFlag": json_to_dict.get( - "advertiseHostRouteFlag" - ), + "advertiseHostRouteFlag": json_to_dict.get("advertiseHostRouteFlag"), "advertiseDefaultRouteFlag": json_to_dict.get( "advertiseDefaultRouteFlag" ), @@ -2663,12 +2644,8 @@ def push_diff_create(self, is_rollback=False): t_conf.update(ENABLE_NETFLOW=json_to_dict.get("ENABLE_NETFLOW")) t_conf.update(NETFLOW_MONITOR=json_to_dict.get("NETFLOW_MONITOR")) t_conf.update(disableRtAuto=json_to_dict.get("disableRtAuto")) - t_conf.update( - routeTargetImport=json_to_dict.get("routeTargetImport") - ) - t_conf.update( - routeTargetExport=json_to_dict.get("routeTargetExport") - ) + t_conf.update(routeTargetImport=json_to_dict.get("routeTargetImport")) + t_conf.update(routeTargetExport=json_to_dict.get("routeTargetExport")) t_conf.update( routeTargetImportEvpn=json_to_dict.get("routeTargetImportEvpn") ) @@ -2692,7 +2669,7 @@ def push_diff_create(self, is_rollback=False): self.failed_to_rollback = True return self.failure(resp) - + def push_diff_attach(self, is_rollback=False): method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" @@ -2749,9 +2726,7 @@ def push_diff_attach(self, is_rollback=False): if item["dot1q"]: nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) else: - nbr_dict["DOT1Q_ID"] = str( - ext_values["DOT1Q_ID"] - ) + nbr_dict["DOT1Q_ID"] = str(ext_values["DOT1Q_ID"]) if item["ipv4_addr"]: nbr_dict["IP_MASK"] = item["ipv4_addr"] @@ -2761,13 +2736,9 @@ def push_diff_attach(self, is_rollback=False): if item["neighbor_ipv4"]: nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] else: - nbr_dict["NEIGHBOR_IP"] = ext_values[ - "NEIGHBOR_IP" - ] + nbr_dict["NEIGHBOR_IP"] = ext_values["NEIGHBOR_IP"] - nbr_dict["NEIGHBOR_ASN"] = ext_values[ - "NEIGHBOR_ASN" - ] + nbr_dict["NEIGHBOR_ASN"] = ext_values["NEIGHBOR_ASN"] if item["ipv6_addr"]: nbr_dict["IPV6_MASK"] = item["ipv6_addr"] @@ -2775,9 +2746,7 @@ def push_diff_attach(self, is_rollback=False): nbr_dict["IPV6_MASK"] = ext_values["IPV6_MASK"] if item["neighbor_ipv6"]: - nbr_dict["IPV6_NEIGHBOR"] = item[ - "neighbor_ipv6" - ] + nbr_dict["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] else: nbr_dict["IPV6_NEIGHBOR"] = ext_values[ "IPV6_NEIGHBOR" @@ -2811,9 +2780,7 @@ def push_diff_attach(self, is_rollback=False): ms_con = {} ms_con["MULTISITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = json.dumps( - ms_con - ) + extension_values["MULTISITE_CONN"] = json.dumps(ms_con) del item @@ -2829,9 +2796,9 @@ def push_diff_attach(self, is_rollback=False): extension_values["VRF_LITE_CONN"] = json.dumps( extension_values["VRF_LITE_CONN"] ) - v_a["extensionValues"] = json.dumps( - extension_values - ).replace(" ", "") + v_a["extensionValues"] = json.dumps(extension_values).replace( + " ", "" + ) if v_a.get("vrf_lite", None) is not None: del v_a["vrf_lite"] @@ -2848,9 +2815,7 @@ def push_diff_attach(self, is_rollback=False): for elem in self.diff_attach: for node in elem["lanAttachList"]: node["fabric"] = self.sn_fab[node["serialNumber"]] - resp = dcnm_send( - self.module, "POST", attach_path, json.dumps(self.diff_attach) - ) + resp = dcnm_send(self.module, "POST", attach_path, json.dumps(self.diff_attach)) self.result["response"].append(resp) fail, self.result["changed"] = self.handle_response(resp, "attach") if fail: @@ -2869,9 +2834,7 @@ def push_diff_deploy(self, is_rollback=False): path = self.paths["GET_VRF"].format(self.fabric) deploy_path = path + "/deployments" - resp = dcnm_send( - self.module, "POST", deploy_path, json.dumps(self.diff_deploy) - ) + resp = dcnm_send(self.module, "POST", deploy_path, json.dumps(self.diff_deploy)) self.result["response"].append(resp) fail, self.result["changed"] = self.handle_response(resp, "deploy") if fail: @@ -2914,7 +2877,6 @@ def wait_for_vrf_del_ready(self): msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) - for vrf in self.diff_delete: ok_to_delete = False path = self.paths["GET_VRF_ATTACH"].format(self.fabric, vrf) @@ -2974,9 +2936,9 @@ def attach_spec(self): spec["export_evpn_rt"] = {"default": "", "type": "str"} if self.params["state"] in ("merged", "overridden", "replaced"): - spec["vrf_lite"] = {"type": "list"} + spec["vrf_lite"] = {"type": "list"} else: - spec["vrf_lite"] = {"default": [], "type": "list"} + spec["vrf_lite"] = {"default": [], "type": "list"} return copy.deepcopy(spec) @@ -3016,20 +2978,34 @@ def vrf_spec(self): spec["ipv6_linklocal_enable"] = {"default": True, "type": "bool"} - spec["loopback_route_tag"] = {"default": 12345, "range_max": 4294967295, "type": "int"} - spec["max_bgp_paths"] = {"default": 1, "range_max": 64, "range_min": 1, "type": "int"} - spec["max_ibgp_paths"] = {"default": 2, "range_max": 64, "range_min": 1, "type": "int"} + spec["loopback_route_tag"] = { + "default": 12345, + "range_max": 4294967295, + "type": "int", + } + spec["max_bgp_paths"] = { + "default": 1, + "range_max": 64, + "range_min": 1, + "type": "int", + } + spec["max_ibgp_paths"] = { + "default": 2, + "range_max": 64, + "range_min": 1, + "type": "int", + } spec["netflow_enable"] = {"default": False, "type": "bool"} spec["nf_monitor"] = {"default": "", "type": "str"} spec["no_rp"] = {"default": False, "type": "bool"} spec["overlay_mcast_group"] = {"default": "", "type": "str"} - - spec["redist_direct_rmap"] = {"default": "FABRIC-RMAP-REDIST-SUBNET", "type": "str"} + spec["redist_direct_rmap"] = { + "default": "FABRIC-RMAP-REDIST-SUBNET", + "type": "str", + } spec["rp_address"] = {"default": "", "type": "str"} - - spec["rp_external"] = {"default": False, "type": "bool"} spec["rp_loopback_id"] = {"default": "", "range_max": 1023, "type": "int"} @@ -3038,30 +3014,34 @@ def vrf_spec(self): spec["source"] = {"default": None, "type": "str"} spec["static_default_route"] = {"default": True, "type": "bool"} - spec["trm_bgw_msite"] = {"default": False, "type": "bool"} spec["trm_enable"] = {"default": False, "type": "bool"} - spec["underlay_mcast_ip"] = {"default": "", "type": "str"} - spec["vlan_id"] = {"range_max": 4094, "type": "int"} spec["vrf_description"] = {"default": "", "type": "str"} spec["vrf_id"] = {"range_max": 16777214, "type": "int"} spec["vrf_intf_desc"] = {"default": "", "type": "str"} - spec["vrf_int_mtu"] = {"default": 9216, "range_max": 9216, "range_min": 68, "type": "int"} + spec["vrf_int_mtu"] = { + "default": 9216, + "range_max": 9216, + "range_min": 68, + "type": "int", + } spec["vrf_name"] = {"length_max": 32, "required": True, "type": "str"} spec["vrf_template"] = {"default": "Default_VRF_Universal", "type": "str"} - spec["vrf_extension_template"] = {"default": "Default_VRF_Extension_Universal", "type": "str"} + spec["vrf_extension_template"] = { + "default": "Default_VRF_Extension_Universal", + "type": "str", + } spec["vrf_vlan_name"] = {"default": "", "type": "str"} - if self.params["state"] in ("merged", "overridden", "replaced"): spec["deploy"] = {"default": True, "type": "bool"} else: From e8616264325cc74871cd81591828ca5256d17225 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 6 Dec 2024 20:26:08 -1000 Subject: [PATCH 26/49] Appease linters --- plugins/modules/dcnm_vrf.py | 100 +++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index e045e0f38..cf44ac0cf 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -804,7 +804,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): ): msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"want[extensionValues] != '' and have[extensionValues] != ''" + msg += "want[extensionValues] != '' and have[extensionValues] != ''" self.log.debug(msg) want_ext_values = want["extensionValues"] @@ -853,7 +853,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): ): msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"want[extensionValues] != '' and have[extensionValues] == ''" + msg += "want[extensionValues] != '' and have[extensionValues] == ''" self.log.debug(msg) found = False @@ -862,7 +862,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): and have["extensionValues"] != "" ): msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"want[extensionValues] == '' and have[extensionValues] != ''" + msg += "want[extensionValues] == '' and have[extensionValues] != ''" self.log.debug(msg) if replace: @@ -871,7 +871,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): found = True else: msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"in else" + msg += "in else" self.log.debug(msg) found = True @@ -885,9 +885,8 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if "isAttached" in want: msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += ( - f"isAttached in want. deleting want[isAttached]" - ) + msg += "isAttached in want. " + msg += "deleting want[isAttached]" self.log.debug(msg) del want["isAttached"] @@ -895,7 +894,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): attach_list.append(want) if want_is_deploy is True: msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"want_is_deploy is True. " + msg += "want_is_deploy is True. " msg += "deleting want[isAttached] " msg += "and setting deploy_vrf to True" self.log.debug(msg) @@ -917,13 +916,13 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): ): if want_is_deploy is True: msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"setting deploy_vrf = True" + msg += "setting deploy_vrf = True" self.log.debug(msg) deploy_vrf = True if self.dict_values_differ(want_inst_values, have_inst_values): msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"dict values differ. Set found = False" + msg += "dict values differ. Set found = False" self.log.debug(msg) found = False @@ -1560,18 +1559,18 @@ def get_want(self): vrf_deploy = vrf.get("deploy", True) if vrf.get("vlan_id"): - vlanId = vrf.get("vlan_id") + vlan_id = vrf.get("vlan_id") else: - vlanId = 0 + vlan_id = 0 - want_create.append(self.update_create_params(vrf, vlanId)) + want_create.append(self.update_create_params(vrf, vlan_id)) if not vrf.get("attach"): continue for attach in vrf["attach"]: deploy = vrf_deploy vrfs.append( - self.update_attach_params(attach, vrf["vrf_name"], deploy, vlanId) + self.update_attach_params(attach, vrf["vrf_name"], deploy, vlan_id) ) if vrfs: @@ -1920,7 +1919,7 @@ def diff_merge_create(self, replace=False): if want_c["vrfName"] == have_c["vrfName"]: vrf_found = True msg = f"{self.class_name}.{method_name}: " - msg += f"Calling diff_for_create with: " + msg += "Calling diff_for_create with: " msg += f"want_c: {json.dumps(want_c, indent=4, sort_keys=True)}, " msg += f"have_c: {json.dumps(have_c, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -1940,7 +1939,7 @@ def diff_merge_create(self, replace=False): if diff: msg = f"{self.class_name}.{method_name}: " - msg += f"Appending diff_create_update with " + msg += "Appending diff_create_update with " msg += f"{json.dumps(diff, indent=4, sort_keys=True)}" self.log.debug(msg) diff_create_update.append(diff) @@ -2089,7 +2088,7 @@ def diff_merge_attach(self, replace=False): want_a["lanAttachList"], have_a["lanAttachList"], replace ) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_for_attach_deploy returned: " + msg += "self.diff_for_attach_deploy returned: " msg += f"deploy_vrf_bool: {deploy_vrf_bool}, " msg += f"diff: {json.dumps(diff, indent=4, sort_keys=True)} " self.log.debug(msg) @@ -2143,7 +2142,7 @@ def diff_merge_attach(self, replace=False): self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_deploy: " + msg += "self.diff_deploy: " msg += f"{json.dumps(self.diff_deploy, indent=4)}" self.log.debug(msg) @@ -2478,6 +2477,11 @@ def get_diff_query(self): self.query = query def push_diff_create_update(self, is_rollback=False): + """ + # Summary + + Send diff_create_update to the controller + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2498,6 +2502,11 @@ def push_diff_create_update(self, is_rollback=False): self.failure(resp) def push_diff_detach(self, is_rollback=False): + """ + # Summary + + Send diff_detach to the controller + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2529,6 +2538,11 @@ def push_diff_detach(self, is_rollback=False): self.failure(resp) def push_diff_undeploy(self, is_rollback=False): + """ + # Summary + + Send diff_undeploy to the controller + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2551,6 +2565,11 @@ def push_diff_undeploy(self, is_rollback=False): self.failure(resp) def push_diff_delete(self, is_rollback=False): + """ + # Summary + + Send diff_delete to the controller + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2584,6 +2603,11 @@ def push_diff_delete(self, is_rollback=False): self.module.fail_json(msg=self.result) def push_diff_create(self, is_rollback=False): + """ + # Summary + + Send diff_create to the controller + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2595,9 +2619,9 @@ def push_diff_create(self, is_rollback=False): for vrf in self.diff_create: json_to_dict = json.loads(vrf["vrfTemplateConfig"]) - vlanId = json_to_dict.get("vrfVlanId", "0") + vlan_id = json_to_dict.get("vrfVlanId", "0") - if vlanId == 0: + if vlan_id == 0: vlan_path = self.paths["GET_VLAN"].format(self.fabric) vlan_data = dcnm_send(self.module, "GET", vlan_path) @@ -2606,12 +2630,12 @@ def push_diff_create(self, is_rollback=False): msg += f"Failure getting autogenerated vlan_id {vlan_data}" self.module.fail_json(msg=msg) - vlanId = vlan_data["DATA"] + vlan_id = vlan_data["DATA"] t_conf = { "vrfSegmentId": vrf["vrfId"], "vrfName": json_to_dict.get("vrfName", ""), - "vrfVlanId": vlanId, + "vrfVlanId": vlan_id, "vrfVlanName": json_to_dict.get("vrfVlanName"), "vrfIntfDescription": json_to_dict.get("vrfIntfDescription"), "vrfDescription": json_to_dict.get("vrfDescription"), @@ -2671,6 +2695,11 @@ def push_diff_create(self, is_rollback=False): self.failure(resp) def push_diff_attach(self, is_rollback=False): + """ + # Summary + + Send diff_attach to the controller + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2698,7 +2727,7 @@ def push_diff_attach(self, is_rollback=False): lite_objects = self.get_vrf_lite_objects(v_a) if not lite_objects.get("DATA"): msg = f"{self.class_name}.{method_name}: " - msg += f"Early return, no lite objects." + msg += "Early return, no lite objects." return lite = lite_objects["DATA"][0]["switchDetailsList"][0][ @@ -2825,6 +2854,11 @@ def push_diff_attach(self, is_rollback=False): self.failure(resp) def push_diff_deploy(self, is_rollback=False): + """ + # Summary + + Send diff_deploy to the controller + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2844,6 +2878,11 @@ def push_diff_deploy(self, is_rollback=False): self.failure(resp) def push_to_remote(self, is_rollback=False): + """ + # Summary + + Send all diffs to the controller + """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -2929,6 +2968,11 @@ def wait_for_vrf_del_ready(self): self.diff_delete.update({vrf: "NA"}) def attach_spec(self): + """ + # Summary + + Return the argument spec for network attachments + """ spec = {} spec["deploy"] = {"default": True, "type": "bool"} spec["ip_address"] = {"required": True, "type": "str"} @@ -2943,6 +2987,11 @@ def attach_spec(self): return copy.deepcopy(spec) def lite_spec(self): + """ + # Summary + + Return the argument spec for VRF LITE parameters + """ spec = {} spec["dot1q"] = {"type": "int"} spec["ipv4_addr"] = {"type": "ipv4_subnet"} @@ -2959,6 +3008,11 @@ def lite_spec(self): return copy.deepcopy(spec) def vrf_spec(self): + """ + # Summary + + Return the argument spec for VRF parameters + """ spec = {} spec["adv_default_routes"] = {"default": True, "type": "bool"} spec["adv_host_routes"] = {"default": False, "type": "bool"} From 3a1a514143626459f427b297c42dc4003b159a01 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sat, 7 Dec 2024 09:09:10 -1000 Subject: [PATCH 27/49] More refactoring 1. update_attach_params() Simplified by: 1. Initialize nbr_dict values to "" 2. Populate the nbr_dict values from the current item from attach["vrf_lite"] 3. Test if any values in nbr_dict are != "" after step 2 4. If no values have been updated, continue 5. De-indent the remaining code 6. (also renamed vlanId to vlan_id) This change also required that we add "IF_NAME" to self.vrf_lite_properties. I verified that this change will not impact the other use for this structure in diff_for_attach_deploy(). 2. diff_for_create() After the refactor of this method in the last commit, it became obvious that code in the if and else clauses were heavily duplicated. Refactored to remove the if/else clause entirely since the only difference between these was whether we skip key "vrfVlanId" when vlan_id_want == 0. This reduces to a simple if statement to populate skip_keys if vlan_id_want == 0. --- plugins/modules/dcnm_vrf.py | 176 ++++++++++++++---------------------- 1 file changed, 69 insertions(+), 107 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index cf44ac0cf..f1aae90b0 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -649,6 +649,7 @@ def __init__(self, module): self.vrf_lite_properties = [ "DOT1Q_ID", + "IF_NAME", "IP_MASK", "IPV6_MASK", "IPV6_NEIGHBOR", @@ -947,7 +948,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): self.log.debug(msg) return attach_list, deploy_vrf - def update_attach_params(self, attach, vrf_name, deploy, vlanId): + def update_attach_params(self, attach, vrf_name, deploy, vlan_id): method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) @@ -992,70 +993,51 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): self.module.fail_json(msg=msg) for item in attach["vrf_lite"]: - if ( - item["interface"] - or item["dot1q"] - or item["ipv4_addr"] - or item["neighbor_ipv4"] - or item["ipv6_addr"] - or item["neighbor_ipv6"] - or item["peer_vrf"] - ): - - # If the playbook contains vrf lite elements add the - # extension values - nbr_dict = {} - if item["interface"]: - nbr_dict["IF_NAME"] = item["interface"] - else: - nbr_dict["IF_NAME"] = "" - - if item["dot1q"]: - nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) - else: - nbr_dict["DOT1Q_ID"] = "" - - if item["ipv4_addr"]: - nbr_dict["IP_MASK"] = item["ipv4_addr"] - else: - nbr_dict["IP_MASK"] = "" - - if item["neighbor_ipv4"]: - nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] - else: - nbr_dict["NEIGHBOR_IP"] = "" - - if item["ipv6_addr"]: - nbr_dict["IPV6_MASK"] = item["ipv6_addr"] - else: - nbr_dict["IPV6_MASK"] = "" - - if item["neighbor_ipv6"]: - nbr_dict["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] - else: - nbr_dict["IPV6_NEIGHBOR"] = "" - if item["peer_vrf"]: - nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] - else: - nbr_dict["PEER_VRF_NAME"] = "" + # If the playbook contains vrf lite elements add the + # extension values + nbr_dict = {} + for param in self.vrf_lite_properties: + nbr_dict[param] = "" + + if item["interface"]: + nbr_dict["IF_NAME"] = item["interface"] + if item["dot1q"]: + nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) + if item["ipv4_addr"]: + nbr_dict["IP_MASK"] = item["ipv4_addr"] + if item["neighbor_ipv4"]: + nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] + if item["ipv6_addr"]: + nbr_dict["IPV6_MASK"] = item["ipv6_addr"] + if item["neighbor_ipv6"]: + nbr_dict["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] + if item["peer_vrf"]: + nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] + + process_attachment = False + for key, value in nbr_dict.items(): + if value != "": + process_attachment = True + if not process_attachment: + continue - nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = "Ext_VRF_Lite_Jython" + nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = "Ext_VRF_Lite_Jython" - msg = f"{self.class_name}.update_attach_params: " - msg += f"nbr_dict: {json.dumps(nbr_dict, indent=4, sort_keys=True)}" - self.log.debug(msg) + msg = f"{self.class_name}.update_attach_params: " + msg += f"nbr_dict: {json.dumps(nbr_dict, indent=4, sort_keys=True)}" + self.log.debug(msg) - vrflite_con = {} - vrflite_con["VRF_LITE_CONN"] = [] - vrflite_con["VRF_LITE_CONN"].append(copy.deepcopy(nbr_dict)) + vrflite_con = {} + vrflite_con["VRF_LITE_CONN"] = [] + vrflite_con["VRF_LITE_CONN"].append(copy.deepcopy(nbr_dict)) - if ext_values["VRF_LITE_CONN"]: - ext_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend( - vrflite_con["VRF_LITE_CONN"] - ) - else: - ext_values["VRF_LITE_CONN"] = vrflite_con + if ext_values["VRF_LITE_CONN"]: + ext_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend( + vrflite_con["VRF_LITE_CONN"] + ) + else: + ext_values["VRF_LITE_CONN"] = vrflite_con ext_values["VRF_LITE_CONN"] = json.dumps(ext_values["VRF_LITE_CONN"]) @@ -1063,7 +1045,7 @@ def update_attach_params(self, attach, vrf_name, deploy, vlanId): attach.update({"fabric": self.fabric}) attach.update({"vrfName": vrf_name}) - attach.update({"vlan": vlanId}) + attach.update({"vlan": vlan_id}) # This flag is not to be confused for deploy of attachment. # "deployment" should be set True for attaching an attachment # and set to False for detaching an attachment @@ -1145,58 +1127,38 @@ def diff_for_create(self, want, have): json_to_dict_want = json.loads(want["vrfTemplateConfig"]) json_to_dict_have = json.loads(have["vrfTemplateConfig"]) - vlanId_want = str(json_to_dict_want.get("vrfVlanId", "")) - - if vlanId_want != "0": + # vlan_id_want drives the conditional below, so we cannot + # remove it here (as we did with the other params that are + # compared in the call to self.dict_values_differ()) + vlan_id_want = str(json_to_dict_want.get("vrfVlanId", "")) - templates_differ = self.dict_values_differ( - json_to_dict_want, json_to_dict_have - ) - - msg = f"{self.class_name}.{method_name}: " - msg += f"templates_differ: {templates_differ}, vlanId_want: {vlanId_want}" - self.log.debug(msg) + skip_keys = [] + if vlan_id_want == "0": + skip_keys = ["vrfVlanId"] + templates_differ = self.dict_values_differ( + json_to_dict_want, json_to_dict_have, skip_keys=skip_keys + ) - if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: - msg = f"{self.class_name}.diff_for_create: " - msg += f"vrf_id for vrf {want['vrfName']} cannot be updated to " - msg += "a different value" - self.module.fail_json(msg) + msg = f"{self.class_name}.{method_name}: " + msg += f"templates_differ: {templates_differ}, vlan_id_want: {vlan_id_want}" + self.log.debug(msg) - elif templates_differ: - conf_changed = True - if want["vrfId"] is None: - # The vrf updates with missing vrfId will have to use existing - # vrfId from the instance of the same vrf on DCNM. - want["vrfId"] = have["vrfId"] - create = want + if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: + msg = f"{self.class_name}.diff_for_create: " + msg += f"vrf_id for vrf {want['vrfName']} cannot be updated to " + msg += "a different value" + self.module.fail_json(msg=msg) - else: - pass + elif templates_differ: + conf_changed = True + if want["vrfId"] is None: + # The vrf updates with missing vrfId will have to use existing + # vrfId from the instance of the same vrf on DCNM. + want["vrfId"] = have["vrfId"] + create = want else: - - # skip vrfVlanId when comparing - templates_differ = self.dict_values_differ( - json_to_dict_want, json_to_dict_have, skip_keys=["vrfVlanId"] - ) - - if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: - msg = f"{self.class_name}.diff_for_create: " - msg += f"vrf_id for vrf {want['vrfName']} cannot be updated to " - msg += "a different value" - self.module.fail_json(msg=msg) - - elif templates_differ: - conf_changed = True - if want["vrfId"] is None: - # The vrf updates with missing vrfId will have to use existing - # vrfId from the instance of the same vrf on DCNM. - want["vrfId"] = have["vrfId"] - create = want - - else: - pass + pass msg = f"{self.class_name}.{method_name}: " msg += f"returning conf_changed: {conf_changed}, " From fed3a7078c133af0e0258488474d462be7169ba6 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sat, 7 Dec 2024 09:14:26 -1000 Subject: [PATCH 28/49] Fix typo Mike's eagle-eyes caught this during review. --- plugins/modules/dcnm_vrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index f1aae90b0..958c51e3a 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -1087,7 +1087,7 @@ def dict_values_differ(self, dict1, dict2, skip_keys=None) -> bool: """ # Summary - Given a two dictionaries and, optionally, a list of keys to skip: + Given two dictionaries and, optionally, a list of keys to skip: - Return True if the values for any (non-skipped) keys differs. - Return False otherwise From 930dbecde661df47fd9a155b09f5705ec69b6af2 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sat, 7 Dec 2024 19:40:49 -1000 Subject: [PATCH 29/49] dcnm_vrf: fix IT files, minor cleanup 1. Worked with Mike to fix dcnm.yaml and main.yaml in tests/integration/targets/dcnm_vrf/tasks. 2. Updated query.yaml so I could debug things. Query IT is now working. 3. dcnm_vrf.py - Added a lot of debug statements and cleaned up a few minor things. --- plugins/modules/dcnm_vrf.py | 349 ++++++++++++------ .../targets/dcnm_vrf/tasks/dcnm.yaml | 10 +- .../targets/dcnm_vrf/tasks/main.yaml | 4 +- .../targets/dcnm_vrf/tests/dcnm/query.yaml | 194 +++++++--- 4 files changed, 374 insertions(+), 183 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 958c51e3a..0cdb53af9 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -804,7 +804,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): and have["extensionValues"] != "" ): - msg = f"ZZZ: {self.class_name}.{method_name}: " + msg = f"{self.class_name}.{method_name}: " msg += "want[extensionValues] != '' and have[extensionValues] != ''" self.log.debug(msg) @@ -852,29 +852,16 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want["extensionValues"] != "" and have["extensionValues"] == "" ): - - msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += "want[extensionValues] != '' and have[extensionValues] == ''" - self.log.debug(msg) - found = False elif ( want["extensionValues"] == "" and have["extensionValues"] != "" ): - msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += "want[extensionValues] == '' and have[extensionValues] != ''" - self.log.debug(msg) - if replace: found = False else: found = True else: - msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += "in else" - self.log.debug(msg) - found = True want_is_deploy = self.to_bool("is_deploy", want) have_is_deploy = self.to_bool("is_deploy", have) @@ -885,20 +872,11 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if have_is_attached != want_is_attached: if "isAttached" in want: - msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += "isAttached in want. " - msg += "deleting want[isAttached]" - self.log.debug(msg) del want["isAttached"] want["deployment"] = True attach_list.append(want) if want_is_deploy is True: - msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += "want_is_deploy is True. " - msg += "deleting want[isAttached] " - msg += "and setting deploy_vrf to True" - self.log.debug(msg) del want["isAttached"] deploy_vrf = True continue @@ -906,23 +884,14 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want_deployment = self.to_bool("deployment", want) have_deployment = self.to_bool("deployment", have) - msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"want_deployment {want_deployment}, " - msg += f"have_deployment {have_deployment}, " - msg += f"want_is_deploy {want_is_deploy}, " - msg += f"have_is_deploy {have_is_deploy}, " - self.log.debug(msg) if (want_deployment != have_deployment) or ( want_is_deploy != have_is_deploy ): if want_is_deploy is True: - msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += "setting deploy_vrf = True" - self.log.debug(msg) deploy_vrf = True if self.dict_values_differ(want_inst_values, have_inst_values): - msg = f"ZZZ: {self.class_name}.{method_name}: " + msg = f"{self.class_name}.{method_name}: " msg += "dict values differ. Set found = False" self.log.debug(msg) @@ -944,7 +913,8 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): msg = f"{self.class_name}.{method_name}: Returning " msg += f"deploy_vrf: {deploy_vrf}, " - msg += f"attach_list: {json.dumps(attach_list, indent=4, sort_keys=True)}" + msg += "attach_list: " + msg += f"{json.dumps(attach_list, indent=4, sort_keys=True)}" self.log.debug(msg) return attach_list, deploy_vrf @@ -1016,15 +986,18 @@ def update_attach_params(self, attach, vrf_name, deploy, vlan_id): nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] process_attachment = False - for key, value in nbr_dict.items(): + for _, value in nbr_dict.items(): if value != "": process_attachment = True if not process_attachment: + msg = f"{self.class_name}.{method_name}: " + msg += "No attachment to process. Skipping." + self.log.debug(msg) continue nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = "Ext_VRF_Lite_Jython" - msg = f"{self.class_name}.update_attach_params: " + msg = f"{self.class_name}.{method_name}: " msg += f"nbr_dict: {json.dumps(nbr_dict, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -1041,6 +1014,11 @@ def update_attach_params(self, attach, vrf_name, deploy, vlan_id): ext_values["VRF_LITE_CONN"] = json.dumps(ext_values["VRF_LITE_CONN"]) + msg = f"{self.class_name}.{method_name}: " + msg += "ext_values: " + msg += f"{json.dumps(ext_values, indent=4, sort_keys=True)}" + self.log.debug(msg) + vrf_ext = True attach.update({"fabric": self.fabric}) @@ -1053,6 +1031,11 @@ def update_attach_params(self, attach, vrf_name, deploy, vlan_id): attach.update({"isAttached": True}) attach.update({"serialNumber": serial}) attach.update({"is_deploy": deploy}) + + msg = f"{self.class_name}.{method_name}: " + msg += f"vrf_ext: {vrf_ext}" + self.log.debug(msg) + if vrf_ext: attach.update({"extensionValues": json.dumps(ext_values).replace(" ", "")}) else: @@ -1079,7 +1062,8 @@ def update_attach_params(self, attach, vrf_name, deploy, vlan_id): del attach["ip_address"] msg = f"{self.class_name}.{method_name}: " - msg += f"attach: {json.dumps(attach, indent=4, sort_keys=True)}" + msg += "attach: " + msg += f"{json.dumps(attach, indent=4, sort_keys=True)}" self.log.debug(msg) return attach @@ -1241,10 +1225,9 @@ def get_vrf_objects(self) -> dict: Retrieve all VRF objects from the controller """ - method = "GET" path = self.paths["GET_VRF"].format(self.fabric) - vrf_objects = dcnm_send(self.module, method, path) + vrf_objects = dcnm_send(self.module, "GET", path) missing_fabric, not_ok = self.handle_response(vrf_objects, "query_dcnm") @@ -1271,11 +1254,10 @@ def get_vrf_lite_objects(self, attach) -> dict: msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) - method = "GET" path = self.paths["GET_VRF_SWITCH"].format( attach["fabric"], attach["vrfName"], attach["serialNumber"] ) - lite_objects = dcnm_send(self.module, method, path) + lite_objects = dcnm_send(self.module, "GET", path) return copy.deepcopy(lite_objects) @@ -1550,15 +1532,18 @@ def get_want(self): self.want_deploy = want_deploy msg = f"{self.class_name}.{method_name}: " - msg += f"self.want_create: {json.dumps(self.want_create, indent=4)}" + msg += "self.want_create: " + msg += f"{json.dumps(self.want_create, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.want_attach: {json.dumps(self.want_attach, indent=4)}" + msg += "self.want_attach: " + msg += f"{json.dumps(self.want_attach, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.want_deploy: {json.dumps(self.want_deploy, indent=4)}" + msg += "self.want_deploy: " + msg += f"{json.dumps(self.want_deploy, indent=4)}" self.log.debug(msg) def get_diff_delete(self): @@ -1627,15 +1612,18 @@ def get_items_to_detach(attach_list): self.diff_delete = diff_delete msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_detach: {json.dumps(self.diff_detach, indent=4)}" + msg += "self.diff_detach: " + msg += f"{json.dumps(self.diff_detach, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_undeploy: {json.dumps(self.diff_undeploy, indent=4)}" + msg += "self.diff_undeploy: " + msg += f"{json.dumps(self.diff_undeploy, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_delete: {json.dumps(self.diff_delete, indent=4)}" + msg += "self.diff_delete: " + msg += f"{json.dumps(self.diff_delete, indent=4)}" self.log.debug(msg) def get_diff_override(self): @@ -1686,27 +1674,33 @@ def get_diff_override(self): self.diff_delete = diff_delete msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_create: {json.dumps(self.diff_create, indent=4)}" + msg += "self.diff_create: " + msg += f"{json.dumps(self.diff_create, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_attach: {json.dumps(self.diff_attach, indent=4)}" + msg += "self.diff_attach: " + msg += f"{json.dumps(self.diff_attach, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_deploy: {json.dumps(self.diff_deploy, indent=4)}" + msg += "self.diff_deploy: " + msg += f"{json.dumps(self.diff_deploy, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_detach: {json.dumps(self.diff_detach, indent=4)}" + msg += "self.diff_detach: " + msg += f"{json.dumps(self.diff_detach, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_undeploy: {json.dumps(self.diff_undeploy, indent=4)}" + msg += "self.diff_undeploy: " + msg += f"{json.dumps(self.diff_undeploy, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_delete: {json.dumps(self.diff_delete, indent=4)}" + msg += "self.diff_delete: " + msg += f"{json.dumps(self.diff_delete, indent=4)}" self.log.debug(msg) def get_diff_replace(self): @@ -1795,15 +1789,18 @@ def get_diff_replace(self): self.diff_deploy = diff_deploy msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_create: {json.dumps(self.diff_create, indent=4)}" + msg += "self.diff_create: " + msg += f"{json.dumps(self.diff_create, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_attach: {json.dumps(self.diff_attach, indent=4)}" + msg += "self.diff_attach: " + msg += f"{json.dumps(self.diff_attach, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_deploy: {json.dumps(self.diff_deploy, indent=4)}" + msg += "self.diff_deploy: " + msg += f"{json.dumps(self.diff_deploy, indent=4)}" self.log.debug(msg) def get_next_vrf_id(self, fabric) -> int: @@ -1822,7 +1819,6 @@ def get_next_vrf_id(self, fabric) -> int: msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) - method = "POST" attempt = 0 vrf_id = None while attempt < 10: @@ -1831,7 +1827,7 @@ def get_next_vrf_id(self, fabric) -> int: if self.dcnm_version > 11: vrf_id_obj = dcnm_send(self.module, "GET", path) else: - vrf_id_obj = dcnm_send(self.module, method, path) + vrf_id_obj = dcnm_send(self.module, "POST", path) missing_fabric, not_ok = self.handle_response(vrf_id_obj, "query_dcnm") @@ -1908,16 +1904,12 @@ def diff_merge_create(self, replace=False): break if not vrf_found: - msg = f"{self.class_name}.{method_name}: " - msg += f"in if not vrf_found with vrf_found {vrf_found}" - self.log.debug(msg) - vrf_id = want_c.get("vrfId", None) if vrf_id is not None: diff_create.append(want_c) else: # vrfId is not provided by user. - # Fetch tje next available vrfId and use it here. + # Fetch the next available vrfId and use it here. vrf_id = self.get_next_vrf_id(self.fabric) want_c.update({"vrfId": vrf_id}) @@ -2002,7 +1994,7 @@ def diff_merge_create(self, replace=False): if self.module.check_mode: continue - # arobel: TODO: The below is not covered by UT + # arobel: TODO: Not covered by UT resp = dcnm_send( self.module, "POST", create_path, json.dumps(want_c) ) @@ -2016,7 +2008,8 @@ def diff_merge_create(self, replace=False): self.diff_create_quick = diff_create_quick msg = f"{self.class_name}.{method_name}: " - msg += f"self.diff_create: {json.dumps(self.diff_create, indent=4)}" + msg += "self.diff_create: " + msg += f"{json.dumps(self.diff_create, indent=4)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: " @@ -2049,11 +2042,6 @@ def diff_merge_attach(self, replace=False): diff, deploy_vrf_bool = self.diff_for_attach_deploy( want_a["lanAttachList"], have_a["lanAttachList"], replace ) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_for_attach_deploy returned: " - msg += f"deploy_vrf_bool: {deploy_vrf_bool}, " - msg += f"diff: {json.dumps(diff, indent=4, sort_keys=True)} " - self.log.debug(msg) if diff: base = want_a.copy() del base["lanAttachList"] @@ -2116,8 +2104,10 @@ def get_diff_merge(self, replace=False): # Special cases: # 1. Auto generate vrfId if its not mentioned by user: - # In this case, we need to query the DCNM to get a usable ID and use it in the payload. - # And also, any such vrf create requests need to be pushed individually(not bulk op). + # - In this case, query the controller for a vrfId and + # use it in the payload. + # - Any such vrf create requests need to be pushed individually + # (not bulk op). self.diff_merge_create(replace) self.diff_merge_attach(replace) @@ -2141,6 +2131,41 @@ def format_diff(self): self.diff_undeploy["vrfNames"].split(",") if self.diff_undeploy else [] ) + msg = f"{self.class_name}.{method_name}: INPUT: " + msg += "diff_create: " + msg += f"{json.dumps(diff_create, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: INPUT: " + msg += "diff_create_quick: " + msg += f"{json.dumps(diff_create_quick, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: INPUT: " + msg += "diff_create_update: " + mag += f"{json.dumps(diff_create_update, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: INPUT: " + msg += "diff_attach: " + msg += f"{json.dumps(diff_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: INPUT: " + msg += "diff_detach: " + msg += f"{json.dumps(diff_detach, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: INPUT: " + msg += "diff_deploy: " + msg += f"{json.dumps(diff_deploy, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: INPUT: " + msg += "diff_undeploy: " + msg += f"{json.dumps(diff_undeploy, indent=4, sort_keys=True)}" + self.log.debug(msg) + diff_create.extend(diff_create_quick) diff_create.extend(diff_create_update) diff_attach.extend(diff_detach) @@ -2148,11 +2173,26 @@ def format_diff(self): for want_d in diff_create: + msg = f"{self.class_name}.{method_name}: " + msg += "want_d: " + msg += f"{json.dumps(want_d, indent=4, sort_keys=True)}" + self.log.debug(msg) + found_a = self.find_dict_in_list_by_key_value( search=diff_attach, key="vrfName", value=want_d["vrfName"] ) - found_c = want_d + msg = f"{self.class_name}.{method_name}: " + msg += "found_a: " + msg += f"{json.dumps(found_a, indent=4, sort_keys=True)}" + self.log.debug(msg) + + found_c = copy.deepcopy(want_d) + + msg = f"{self.class_name}.{method_name}: " + msg += "found_c: PRE_UPDATE: " + msg += f"{json.dumps(found_c, indent=4, sort_keys=True)}" + self.log.debug(msg) src = found_c["source"] found_c.update({"vrf_name": found_c["vrfName"]}) @@ -2248,9 +2288,17 @@ def format_diff(self): del found_c["serviceVrfTemplate"] del found_c["vrfTemplateConfig"] + msg = f"{self.class_name}.{method_name}: " + msg += "found_c: POST_UPDATE: " + msg += f"{json.dumps(found_c, indent=4, sort_keys=True)}" + self.log.debug(msg) + if diff_deploy and found_c["vrf_name"] in diff_deploy: diff_deploy.remove(found_c["vrf_name"]) if not found_a: + msg = f"{self.class_name}.{method_name}: " + msg += "not found_a. Appending found_c to diff." + self.log.debug(msg) diff.append(found_c) continue @@ -2267,6 +2315,10 @@ def format_diff(self): attach_d.update({"deploy": a_w["deployment"]}) found_c["attach"].append(attach_d) + msg = f"{self.class_name}.{method_name}: " + msg += "Appending found_c to diff." + self.log.debug(msg) + diff.append(found_c) diff_attach.remove(found_a) @@ -2278,7 +2330,6 @@ def format_diff(self): for a_w in attach: attach_d = {} - for k, v in self.ip_sn.items(): if v == a_w["serialNumber"]: attach_d.update({"ip_address": k}) @@ -2298,7 +2349,7 @@ def format_diff(self): new_deploy_dict = {"vrf_name": vrf} diff.append(new_deploy_dict) - self.diff_input_format = diff + self.diff_input_format = copy.deepcopy(diff) msg = f"{self.class_name}.{method_name}: " msg += "self.diff_input_format: " @@ -2310,9 +2361,8 @@ def get_diff_query(self): msg = f"{self.class_name}.{method_name}: ENTERED" self.log.debug(msg) - method = "GET" path = self.paths["GET_VRF"].format(self.fabric) - vrf_objects = dcnm_send(self.module, method, path) + vrf_objects = dcnm_send(self.module, "GET", path) missing_fabric, not_ok = self.handle_response(vrf_objects, "query_dcnm") if ( @@ -2346,12 +2396,11 @@ def get_diff_query(self): item["parent"] = vrf # Query the Attachment for the found VRF - method = "GET" path = self.paths["GET_VRF_ATTACH"].format( self.fabric, vrf["vrfName"] ) - vrf_attach_objects = dcnm_send(self.module, method, path) + vrf_attach_objects = dcnm_send(self.module, "GET", path) missing_fabric, not_ok = self.handle_response( vrf_attach_objects, "query_dcnm" @@ -2400,10 +2449,9 @@ def get_diff_query(self): item["parent"] = vrf # Query the Attachment for the found VRF - method = "GET" path = self.paths["GET_VRF_ATTACH"].format(self.fabric, vrf["vrfName"]) - vrf_attach_objects = dcnm_send(self.module, method, path) + vrf_attach_objects = dcnm_send(self.module, "GET", path) missing_fabric, not_ok = self.handle_response(vrf_objects, "query_dcnm") @@ -2450,11 +2498,10 @@ def push_diff_create_update(self, is_rollback=False): path = self.paths["GET_VRF"].format(self.fabric) - method = "PUT" if self.diff_create_update: for vrf in self.diff_create_update: update_path = f"{path}/{vrf['vrfName']}" - resp = dcnm_send(self.module, method, update_path, json.dumps(vrf)) + resp = dcnm_send(self.module, "PUT", update_path, json.dumps(vrf)) self.result["response"].append(resp) fail, self.result["changed"] = self.handle_response(resp, "create") if fail: @@ -2479,16 +2526,17 @@ def push_diff_detach(self, is_rollback=False): path = self.paths["GET_VRF"].format(self.fabric) detach_path = path + "/attachments" - # Update the fabric name to specific fabric to which the switches belong for multisite fabric. + # For multiste fabric, update the fabric name to the child fabric + # containing the switches if self.fabric_type == "MFD": for elem in self.diff_detach: for node in elem["lanAttachList"]: node["fabric"] = self.sn_fab[node["serialNumber"]] - for d_a in self.diff_detach: - for v_a in d_a["lanAttachList"]: - if "is_deploy" in v_a.keys(): - del v_a["is_deploy"] + for diff_attach in self.diff_detach: + for vrf_attach in diff_attach["lanAttachList"]: + if "is_deploy" in vrf_attach.keys(): + del vrf_attach["is_deploy"] resp = dcnm_send(self.module, "POST", detach_path, json.dumps(self.diff_detach)) self.result["response"].append(resp) @@ -2667,17 +2715,31 @@ def push_diff_attach(self, is_rollback=False): self.log.debug(msg) if not self.diff_attach: + msg = f"{self.class_name}.{method_name}: " + msg += "Early return. self.diff_attach is empty. " + msg += f"{json.dumps(self.diff_attach, indent=4, sort_keys=True)}" return - for d_a in self.diff_attach: - for v_a in d_a["lanAttachList"]: - v_a.update(vlan=0) - if "is_deploy" in v_a.keys(): - del v_a["is_deploy"] - if v_a.get("vrf_lite"): + for diff_attach in self.diff_attach: + msg = f"{self.class_name}.{method_name}: " + msg += "diff_attach: " + msg += f"{json.dumps(diff_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) + + for vrf_attach in diff_attach["lanAttachList"]: + msg = f"{self.class_name}.{method_name}: " + msg += "vrf_attach: " + msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) + + vrf_attach.update(vlan=0) + if "is_deploy" in vrf_attach.keys(): + del vrf_attach["is_deploy"] + if vrf_attach.get("vrf_lite"): for ip, ser in self.ip_sn.items(): - if ser == v_a["serialNumber"]: - """Before apply the vrf_lite config, need double check if the switch role is started wth Border""" + if ser == vrf_attach["serialNumber"]: + # Before applying the vrf_lite config, verify that + # the switch role beings with "border" role = self.inventory_data[ip].get("switchRole") r = re.search(r"\bborder\b", role.lower()) if not r: @@ -2686,7 +2748,13 @@ def push_diff_attach(self, is_rollback=False): msg += f"switch {ip} with role {role}" self.module.fail_json(msg=msg) - lite_objects = self.get_vrf_lite_objects(v_a) + lite_objects = self.get_vrf_lite_objects(vrf_attach) + + msg = f"{self.class_name}.{method_name}: " + msg += "lite_objects: " + msg += f"{json.dumps(lite_objects, indent=4, sort_keys=True)}" + self.log.debug(msg) + if not lite_objects.get("DATA"): msg = f"{self.class_name}.{method_name}: " msg += "Early return, no lite objects." @@ -2695,8 +2763,9 @@ def push_diff_attach(self, is_rollback=False): lite = lite_objects["DATA"][0]["switchDetailsList"][0][ "extensionPrototypeValues" ] - msg = f"ZZZ: {self.class_name}.{method_name}: " - msg += f"lite: {json.dumps(lite, indent=4, sort_keys=True)}" + msg = f"{self.class_name}.{method_name}: " + msg += "lite: " + msg += f"{json.dumps(lite, indent=4, sort_keys=True)}" self.log.debug(msg) ext_values = None @@ -2709,7 +2778,7 @@ def push_diff_attach(self, is_rollback=False): continue ext_values = ext_l["extensionValues"] ext_values = ast.literal_eval(ext_values) - for item in v_a.get("vrf_lite"): + for item in vrf_attach.get("vrf_lite"): if item["interface"] == ext_values["IF_NAME"]: nbr_dict = {} nbr_dict["IF_NAME"] = item["interface"] @@ -2778,7 +2847,7 @@ def push_diff_attach(self, is_rollback=False): if ext_values is None: for ip, ser in self.ip_sn.items(): # arobel TODO: Not covered by UT - if ser == v_a["serialNumber"]: + if ser == vrf_attach["serialNumber"]: msg = f"{self.class_name}.{method_name}: " msg += "No VRF LITE capable interfaces found " msg += f"on this switch {ip}" @@ -2787,15 +2856,15 @@ def push_diff_attach(self, is_rollback=False): extension_values["VRF_LITE_CONN"] = json.dumps( extension_values["VRF_LITE_CONN"] ) - v_a["extensionValues"] = json.dumps(extension_values).replace( - " ", "" - ) - if v_a.get("vrf_lite", None) is not None: - del v_a["vrf_lite"] + vrf_attach["extensionValues"] = json.dumps( + extension_values + ).replace(" ", "") + if vrf_attach.get("vrf_lite", None) is not None: + del vrf_attach["vrf_lite"] else: - if "vrf_lite" in v_a.keys(): - del v_a["vrf_lite"] + if "vrf_lite" in vrf_attach.keys(): + del vrf_attach["vrf_lite"] path = self.paths["GET_VRF"].format(self.fabric) attach_path = path + "/attachments" @@ -3026,36 +3095,30 @@ def vrf_spec(self): spec["rp_loopback_id"] = {"default": "", "range_max": 1023, "type": "int"} spec["service_vrf_template"] = {"default": None, "type": "str"} - spec["source"] = {"default": None, "type": "str"} spec["static_default_route"] = {"default": True, "type": "bool"} spec["trm_bgw_msite"] = {"default": False, "type": "bool"} - spec["trm_enable"] = {"default": False, "type": "bool"} spec["underlay_mcast_ip"] = {"default": "", "type": "str"} spec["vlan_id"] = {"range_max": 4094, "type": "int"} spec["vrf_description"] = {"default": "", "type": "str"} - spec["vrf_id"] = {"range_max": 16777214, "type": "int"} spec["vrf_intf_desc"] = {"default": "", "type": "str"} - spec["vrf_int_mtu"] = { "default": 9216, "range_max": 9216, "range_min": 68, "type": "int", } - spec["vrf_name"] = {"length_max": 32, "required": True, "type": "str"} spec["vrf_template"] = {"default": "Default_VRF_Universal", "type": "str"} spec["vrf_extension_template"] = { "default": "Default_VRF_Extension_Universal", "type": "str", } - spec["vrf_vlan_name"] = {"default": "", "type": "str"} if self.params["state"] in ("merged", "overridden", "replaced"): @@ -3077,10 +3140,29 @@ def validate_input(self): lite_spec = self.lite_spec() vrf_spec = self.vrf_spec() + msg = f"{self.class_name}.{method_name}: " + msg += "attach_spec: " + msg += f"{json.dumps(attach_spec, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += "lite_spec: " + msg += f"{json.dumps(lite_spec, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += "vrf_spec: " + msg += f"{json.dumps(vrf_spec, indent=4, sort_keys=True)}" + self.log.debug(msg) + if state in ("merged", "overridden", "replaced"): - msg = None + fail_msg_list = [] if self.config: for vrf in self.config: + msg = f"{self.class_name}.{method_name}: state {state}: " + msg += "self.config[vrf]: " + msg += f"{json.dumps(vrf, indent=4, sort_keys=True)}" + self.log.debug(msg) # A few user provided vrf parameters need special handling # Ignore user input for src and hard code it to None vrf["source"] = None @@ -3088,18 +3170,25 @@ def validate_input(self): vrf["service_vrf_template"] = None if "vrf_name" not in vrf: - msg = "vrf_name is mandatory under vrf parameters" + fail_msg_list.append( + "vrf_name is mandatory under vrf parameters" + ) if isinstance(vrf.get("attach"), list): for attach in vrf["attach"]: if "ip_address" not in attach: - msg = "ip_address is mandatory under attach parameters" + fail_msg_list.append( + "ip_address is mandatory under attach parameters" + ) else: if state in ("merged", "replaced"): - msg = f"{self.class_name}.{method_name}: " - msg += f"config element is mandatory for {state} state" + fail_msg_list.append( + f"config element is mandatory for {state} state" + ) - if msg: + if fail_msg_list: + msg = f"{self.class_name}.{method_name}: " + msg += ",".join(fail_msg_list) self.module.fail_json(msg=msg) if self.config: @@ -3107,12 +3196,22 @@ def validate_input(self): self.config, vrf_spec ) for vrf in valid_vrf: + + msg = f"{self.class_name}.{method_name}: state {state}: " + msg += "valid_vrf[vrf]: " + msg += f"{json.dumps(vrf, indent=4, sort_keys=True)}" + self.log.debug(msg) + if vrf.get("attach"): for entry in vrf.get("attach"): entry["deploy"] = vrf["deploy"] valid_att, invalid_att = validate_list_of_dicts( vrf["attach"], attach_spec ) + msg = f"{self.class_name}.{method_name}: state {state}: " + msg += "valid_att: " + msg += f"{json.dumps(valid_att, indent=4, sort_keys=True)}" + self.log.debug(msg) vrf["attach"] = valid_att invalid_params.extend(invalid_att) @@ -3121,6 +3220,13 @@ def validate_input(self): valid_lite, invalid_lite = validate_list_of_dicts( lite["vrf_lite"], lite_spec ) + msg = ( + f"{self.class_name}.{method_name}: state {state}: " + ) + msg += "valid_lite: " + msg += f"{json.dumps(valid_lite, indent=4, sort_keys=True)}" + self.log.debug(msg) + lite["vrf_lite"] = valid_lite invalid_params.extend(invalid_lite) self.validated.append(vrf) @@ -3217,7 +3323,8 @@ def failure(self, resp): self.push_to_remote(True) if self.failed_to_rollback: - msg1 = "FAILED - Attempted rollback of the task has failed, may need manual intervention" + msg1 = "FAILED - Attempted rollback of the task has failed, " + msg1 += "may need manual intervention" else: msg1 = "SUCCESS - Attempted rollback of the task has succeeded" diff --git a/tests/integration/targets/dcnm_vrf/tasks/dcnm.yaml b/tests/integration/targets/dcnm_vrf/tasks/dcnm.yaml index 0154c6721..90fdd832c 100644 --- a/tests/integration/targets/dcnm_vrf/tasks/dcnm.yaml +++ b/tests/integration/targets/dcnm_vrf/tasks/dcnm.yaml @@ -16,8 +16,16 @@ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" tags: sanity +- name: debug + debug: + var: test_items + +- name: debug + debug: + var: testcase + - name: run test cases (connection=httpapi) - include: "{{ test_case_to_run }} ansible_connection=httpapi connection={{ dcnm }}" + include_tasks: "{{ test_case_to_run }}" with_items: "{{ test_items }}" loop_control: loop_var: test_case_to_run diff --git a/tests/integration/targets/dcnm_vrf/tasks/main.yaml b/tests/integration/targets/dcnm_vrf/tasks/main.yaml index e2c92c5f8..22b634ff4 100644 --- a/tests/integration/targets/dcnm_vrf/tasks/main.yaml +++ b/tests/integration/targets/dcnm_vrf/tasks/main.yaml @@ -38,4 +38,6 @@ - 'controller_version != "Unable to determine controller version"' tags: sanity -- { include: dcnm.yaml, tags: ['dcnm'] } +- name: Import Role Tasks + ansible.builtin.import_tasks: dcnm.yaml + tags: ['dcnm'] \ No newline at end of file diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml index 76115d8d5..a4680f3e8 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml @@ -1,3 +1,19 @@ +############################################## +## REQUIRED VARS ## +############################################## +# test_fabric +# A VXLAN_EVPN fabric +# +# switch_1 +# A border switch +# +# switch_2 +# A border switch +# +# interface_1 +# Ethernet interface on switch_1 and switch_2 +# used to test VRF LITE configuration. + ############################################## ## SETUP ## ############################################## @@ -10,7 +26,7 @@ rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" when: controller_version >= "12" -- name: QUERY - Verify if fabric is deployed. +- name: SETUP.1 - QUERY - Verify if fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" @@ -20,12 +36,12 @@ that: - 'result.response.DATA != None' -- name: QUERY - Clean up any existing vrfs +- name: SETUP.2 - QUERY - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted -- name: QUERY - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: SETUP.3 - QUERY - Create, Attach and Deploy new VRF - VLAN cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -36,12 +52,12 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: SETUP.4 - Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -51,6 +67,10 @@ retries: 30 delay: 2 +- name: debug result var SETUP.4 + debug: + var: result + - assert: that: - 'result.changed == true' @@ -61,15 +81,15 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -############################################### -### QUERY ## -############################################### +# ############################################### +# ### QUERY ## +# ############################################### -- name: QUERY - Query the VRF +- name: TEST.1 - QUERY - Query the VRF cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -80,45 +100,83 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_1 + +- name: debug result_1 TEST.1 + debug: + var: result_1 - assert: that: - - 'result.changed == false' - - 'result.response[0].parent.vrfName == "ansible-vrf-int1"' - - 'result.response[0].parent.vrfId == 9008011' - - 'result.response[0].parent.vrfStatus == "DEPLOYED"' - - 'result.response[0].attach[0].switchDetailsList[0].islanAttached == true' - - 'result.response[0].attach[0].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - - 'result.response[0].attach[0].switchDetailsList[0].vlan == 500' - - 'result.response[0].attach[1].switchDetailsList[0].islanAttached == true' - - 'result.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - - 'result.response[0].attach[1].switchDetailsList[0].vlan == 500' + - 'result_1.changed == false' + - 'result_1.response[0].parent.vrfName == "ansible-vrf-int1"' + - 'result_1.response[0].parent.vrfId == 9008011' + - 'result_1.response[0].parent.vrfStatus == "DEPLOYED"' + - 'result_1.response[0].attach[0].switchDetailsList[0].islanAttached == true' + - 'result_1.response[0].attach[0].switchDetailsList[0].lanAttachedState == "DEPLOYED"' + - 'result_1.response[0].attach[0].switchDetailsList[0].vlan == 500' + - 'result_1.response[0].attach[1].switchDetailsList[0].islanAttached == true' + - 'result_1.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' + - 'result_1.response[0].attach[1].switchDetailsList[0].vlan == 500' -- name: QUERY - Clean up existing vrfs +- name: TEST.2 - QUERY - Clean up existing vrfs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted - register: result + register: result_2 + +- name: debug result_2 TEST.2 + debug: + var: result_2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[1].MESSAGE == "OK"' - - 'result.response[2].RETURN_CODE == 200' - - 'result.response[2].METHOD == "DELETE"' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == false' - - 'result.diff[0].attach[1].deploy == false' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_2.changed == true' + - 'result_2.response[0].RETURN_CODE == 200' + - 'result_2.response[1].RETURN_CODE == 200' + - 'result_2.response[1].MESSAGE == "OK"' + - 'result_2.response[2].RETURN_CODE == 200' + - 'result_2.response[2].METHOD == "DELETE"' + - '(result_2.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2.diff[0].attach[0].deploy == false' + - 'result_2.diff[0].attach[1].deploy == false' + - 'result_2.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.3 - QUERY - Create, Attach and Deploy new VRF - VLAN + cisco.dcnm.dcnm_vrf: + fabric: "{{ test_fabric }}" + state: merged + config: + - vrf_name: ansible-vrf-int2 + vrf_id: 9008012 + vrf_template: Default_VRF_Universal + vrf_extension_template: Default_VRF_Extension_Universal + vlan_id: 1500 + attach: + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" + deploy: true + register: result_3 + +- name: debug result_3 TEST.3 + debug: + var: result_3 + +- name: TEST.4 - Query fabric state until vrfStatus transitions to DEPLOYED state + cisco.dcnm.dcnm_vrf: + fabric: "{{ test_fabric }}" + state: query + register: query_result + until: + - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + retries: 30 + delay: 2 -- name: QUERY - Create, Attach and Deploy new VRF - VRF/VRF LITE EXTENSION Provided by the User in one switch +- name: TEST.5 - QUERY - Create, Attach and Deploy new VRF - VRF/VRF LITE EXTENSION for one switch cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -129,20 +187,24 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 1500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # peer_vrf is mandatory - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_1 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true - register: result + register: result_4 + +- name: debug result_4 TEST.5 (before query) + debug: + var: result_4 -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST - Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -152,19 +214,31 @@ retries: 30 delay: 2 +- name: debug result_4 TEST.5 (after query) + debug: + var: result_4 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int2"' + - 'result_3.changed == true' + - 'result_3.response[0].RETURN_CODE == 200' + - 'result_3.response[1].RETURN_CODE == 200' + - 'result_3.response[2].RETURN_CODE == 200' + - '(result_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3.diff[0].attach[0].deploy == true' + - '"{{ switch_1 }}" in result_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' + +- assert: + that: + - 'result_4.changed == true' + - 'result_4.response[0].RETURN_CODE == 200' + - 'result_4.response[1].RETURN_CODE == 200' + - '(result_4.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - 'result_4.diff[0].attach[0].deploy == true' + - '"{{ switch_2 }}" in result_4.diff[0].attach[0].ip_address' + - 'result_4.diff[0].vrf_name == "ansible-vrf-int2"' - name: QUERY - Query the - VRF/VRF LITE EXTENSION Provided by the User in one switch cisco.dcnm.dcnm_vrf: @@ -177,11 +251,11 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 1500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # peer_vrf is mandatory - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_1 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -233,8 +307,8 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result From d83d4fce12338cd62798bb1d2f6c086dcf015931 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sat, 7 Dec 2024 20:59:15 -1000 Subject: [PATCH 30/49] Fix UT failures 1. dcnm_vrf.py: Fix mispelled var 2. test_dcnm_vrf.py: Update assert to match error message. --- plugins/modules/dcnm_vrf.py | 2 +- tests/unit/modules/dcnm/test_dcnm_vrf.py | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 0cdb53af9..7ef066ea9 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -2143,7 +2143,7 @@ def format_diff(self): msg = f"{self.class_name}.{method_name}: INPUT: " msg += "diff_create_update: " - mag += f"{json.dumps(diff_create_update, indent=4, sort_keys=True)}" + msg += f"{json.dumps(diff_create_update, indent=4, sort_keys=True)}" self.log.debug(msg) msg = f"{self.class_name}.{method_name}: INPUT: " diff --git a/tests/unit/modules/dcnm/test_dcnm_vrf.py b/tests/unit/modules/dcnm/test_dcnm_vrf.py index ee60ea9e1..b505c5fb6 100644 --- a/tests/unit/modules/dcnm/test_dcnm_vrf.py +++ b/tests/unit/modules/dcnm/test_dcnm_vrf.py @@ -1269,17 +1269,16 @@ def test_dcnm_vrf_validation(self): ) ) result = self.execute_module(changed=False, failed=True) - self.assertEqual( - result["msg"], "ip_address is mandatory under attach parameters" - ) + msg = "DcnmVrf.validate_input: " + msg += "vrf_name is mandatory under vrf parameters," + msg += "ip_address is mandatory under attach parameters" + self.assertEqual(result["msg"], msg) def test_dcnm_vrf_validation_no_config(self): set_module_args(dict(state="merged", fabric="test_fabric", config=[])) result = self.execute_module(changed=False, failed=True) msg = "DcnmVrf.validate_input: config element is mandatory for merged state" - self.assertEqual( - result["msg"], msg - ) + self.assertEqual(result["msg"], msg) def test_dcnm_vrf_12check_mode(self): self.version = 12 From 832a3e82cb17c24314422fb0e3c259dba0e87386 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sun, 8 Dec 2024 07:27:23 -1000 Subject: [PATCH 31/49] Appease pylint --- plugins/modules/dcnm_vrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 7ef066ea9..1b377c64e 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -986,7 +986,7 @@ def update_attach_params(self, attach, vrf_name, deploy, vlan_id): nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] process_attachment = False - for _, value in nbr_dict.items(): + for value in nbr_dict.values(): if value != "": process_attachment = True if not process_attachment: From cf27b33f99b0afa29325605d4ddbc45f7a8ceac5 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sun, 8 Dec 2024 09:55:26 -1000 Subject: [PATCH 32/49] Cleanup IT deleted, query, merged NOTE: All three of these tests are working with the current set of changes up to this point. 1. Add REQUIRED VARS section 2. Update task titles for consistency 3. Add a wait_for task to the SETUP section in case any VRFs exist with vrf-lite extensions prior to running the tests. This wait_for will be skipped if the preceeding "Delete all VRFs" task result.changed value is false. 4. Standardize var names - switch_1 - border switch - switch_2 - border switch - switch_3 - non-border switch - interface_1 --- .../targets/dcnm_vrf/tests/dcnm/deleted.yaml | 113 +++++++++----- .../targets/dcnm_vrf/tests/dcnm/merged.yaml | 141 +++++++++++------- .../targets/dcnm_vrf/tests/dcnm/query.yaml | 35 +++-- 3 files changed, 186 insertions(+), 103 deletions(-) diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml index 489b94c8c..9c7269853 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml @@ -1,3 +1,19 @@ +############################################## +## REQUIRED VARS ## +############################################## +# test_fabric +# A VXLAN_EVPN fabric +# +# switch_1 +# A border switch +# +# switch_2 +# A border switch +# +# interface_1 +# Ethernet interface on switch_1 and switch_2 +# used to test VRF LITE configuration. + ############################################## ## SETUP ## ############################################## @@ -10,7 +26,7 @@ rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" when: controller_version >= "12" -- name: DELETED - Verify if fabric is deployed. +- name: SETUP.1 - DELETED - Verify fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" @@ -20,12 +36,18 @@ that: - 'result.response.DATA != None' -- name: DELETED - Clean up any existing vrfs +- name: SETUP.2 - DELETED - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted + register: result_setup_2 + +- name: SETUP.2a - DELETED - Wait 40 seconds for controller and switch to sync + wait_for: + timeout: 40 + when: result_setup_2.changed == true -- name: DELETED - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: SETUP.3 - DELETED - Create, Attach and Deploy VLAN+VRF cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -36,12 +58,12 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: SETUP.4 - DELETED - Query for vrfStatus transition to DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -61,15 +83,15 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' ############################################### ### DELETED ## ############################################### -- name: DELETED - Delete VRF using deleted state +- name: TEST.1 - DELETED - Delete VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: &conf fabric: "{{ test_fabric }}" state: deleted @@ -94,7 +116,11 @@ - 'result.diff[0].attach[1].deploy == false' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: DELETED - conf - Idempotence +- name: TEST.1a - DELETED - Wait 40 seconds for controller and switch to sync + wait_for: + timeout: 40 + +- name: TEST.2 - DELETED - conf - Idempotence cisco.dcnm.dcnm_vrf: *conf register: result @@ -104,7 +130,7 @@ - 'result.response|length == 0' - 'result.diff|length == 0' -- name: DELETED - Create, Attach and Deploy new VRF - VLAN/VRF LITE EXTENSION Provided by the User in one switch +- name: TEST.3 - DELETED - Create, Attach and Deploy new VLAN+VRF+LITE EXTENSION 1x switch cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -115,11 +141,11 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_1 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -128,7 +154,7 @@ deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.4 - DELETED - Query for vrfStatus transition to DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -148,11 +174,11 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: DELETED - Delete VRF/VRF LITE - VLAN/VRF LITE EXTENSION Provided by the User in one switch +- name: TEST.5 - DELETED - Delete VRF+LITE EXTENSION 1x switch cisco.dcnm.dcnm_vrf: &conf1 fabric: "{{ test_fabric }}" state: deleted @@ -163,16 +189,16 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_1 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 2 # dot1q can be got from dcnm + dot1q: 2 # controller can provide dot1q deploy: true register: result @@ -190,7 +216,14 @@ - 'result.diff[0].attach[1].deploy == false' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: DELETED - conf1 - Idempotence +- name: TEST.5a - DELETED - Wait 40 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 40 + +- name: TEST.6 - DELETED - conf1 - Idempotence cisco.dcnm.dcnm_vrf: *conf1 register: result @@ -200,14 +233,7 @@ - 'result.response|length == 0' - 'result.diff|length == 0' -- name: QUERY - sleep for 40 seconds for DCNM to completely update the state - # The vrf lite profile removal returns ok for deployment, but the switch takes time to remove - # the profile so wait for some time before creating a new vrf, else the switch goes into - # OUT-OF-SYNC state - wait_for: - timeout: 40 - -- name: DELETED - Create, Attach and Deploy new VRF - VLAN/VRF LITE EXTENSION Provided by the User in one switch +- name: TEST.7 - DELETED - Create, Attach, Deploy VRF+LITE EXTENSION 1x switch cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -218,11 +244,11 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_1 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -231,7 +257,7 @@ deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.8 - DELETED - Query for vrfStatus transition to DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -251,11 +277,11 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: DELETED - Delete VRF/VRF LITE - WITHOUT ANY CONFIG ELEMENT +- name: TEST.9 - DELETED - Delete VRF+LITE EXTENSION - empty config element cisco.dcnm.dcnm_vrf: &conf2 fabric: "{{ test_fabric }}" state: deleted @@ -276,7 +302,14 @@ - 'result.diff[0].attach[1].deploy == false' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: DELETED - conf1 - Idempotence +- name: TEST.10a - DELETED - Wait 40 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 40 + +- name: TEST.10 - DELETED - conf1 - Idempotence cisco.dcnm.dcnm_vrf: *conf2 register: result @@ -290,7 +323,7 @@ #### CLEAN-UP ## ################################################ -- name: DELETED - Clean up any existing vrfs +- name: CLEANUP.1 - DELETED - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml index f2d7afbc8..0911b7282 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml @@ -1,3 +1,22 @@ +############################################## +## REQUIRED VARS ## +############################################## +# test_fabric +# A VXLAN_EVPN fabric +# +# switch_1 +# A border switch +# +# switch_2 +# A border switch +# +# switch_3 +# A non-border switch +# +# interface_1 +# Ethernet interface on switch_1 and switch_2 +# used to test VRF LITE configuration. + ############################################## ## SETUP ## ############################################## @@ -10,7 +29,7 @@ rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" when: controller_version >= "12" -- name: MERGED - Verify if fabric is deployed. +- name: SETUP.1 - MERGED - Verify fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" @@ -20,16 +39,25 @@ that: - 'result.response.DATA != None' -- name: MERGED - Clean up any existing vrfs +- name: SETUP.2 - MERGED - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted + register: result_setup_2 + +- name: SETUP.3 - MERGED - Wait 40 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 40 + when: result_setup_2.changed == true ############################################### ### MERGED ## ############################################### -- name: MERGED - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: TEST.1 - MERGED - Create, Attach, Deploy VLAN+VRF cisco.dcnm.dcnm_vrf: &conf fabric: "{{ test_fabric }}" state: merged @@ -40,12 +68,12 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.2 - MERGED - Query for vrfStatus transition to DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -65,11 +93,11 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: MERGED - conf1 - Idempotence +- name: TEST.3 - MERGED - conf1 - Idempotence cisco.dcnm.dcnm_vrf: *conf register: result @@ -78,12 +106,12 @@ - 'result.changed == false' - 'result.response|length == 0' -- name: MERGED - Clean up any existing vrfs +- name: TEST.4 - MERGED - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted -- name: MERGED - Create, Attach and Deploy new VRF - VLAN Provided by the DCNM +- name: TEST.5 - MERGED - Create, Attach, Deploy VLAN+VRF (controller provided) cisco.dcnm.dcnm_vrf: &conf2 fabric: "{{ test_fabric }}" state: merged @@ -93,12 +121,12 @@ vrf_template: Default_VRF_Universal vrf_extension_template: Default_VRF_Extension_Universal attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.6 - MERGED - Query for vrfStatus transition to DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -118,11 +146,11 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: MERGED - conf2 - Idempotence +- name: TEST.7 - MERGED - conf2 - Idempotence cisco.dcnm.dcnm_vrf: *conf2 register: result @@ -131,12 +159,18 @@ - 'result.changed == false' - 'result.response|length == 0' -- name: MERGED - Clean up any existing vrfs +- name: TEST.8 - MERGED - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted -- name: MERGED - Create, Attach and Deploy new VRF - VLAN/VRF LITE EXTENSION Provided by the User in one switch +- name: TEST.8a - MERGED - Wait 40 seconds for controller and switch to sync + # While vrf-lite extension was not configured above, we still hit VRF + # OUT-OF-SYNC. Let's see if waiting helps here too. + wait_for: + timeout: 40 + +- name: TEST.9 - MERGED - Create, Attach, Deploy VLAN+VRF+LITE EXTENSION 1x switch (user provided) cisco.dcnm.dcnm_vrf: &conf3 fabric: "{{ test_fabric }}" state: merged @@ -147,11 +181,11 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_1 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -160,7 +194,7 @@ deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.10 - MERGED - Query for vrfStatus transition to DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -180,11 +214,11 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: MERGED - conf3 - Idempotence +- name: TEST.11 - MERGED - conf3 - Idempotence cisco.dcnm.dcnm_vrf: *conf3 register: result @@ -193,19 +227,22 @@ - 'result.changed == false' - 'result.response|length == 0' -- name: MERGED - Clean up any existing vrfs +- name: TEST.12 - MERGED - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted -- name: MERGED - sleep for 40 seconds for DCNM to completely remove lite profile - # The vrf lite profile removal returns ok for deployment, but the switch takes time to remove - # the profile so wait for some time before creating a new vrf, else the switch goes into - # OUT-OF-SYNC state +- name: TEST.12a - MERGED - Wait 40 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: timeout: 40 -- name: MERGED - Create, Attach and Deploy new VRF - VRF/VRF LITE EXTENSION Provided by the DCNM - one optional - Rest are populated from DCNM +# Original wording in case I misinterpreted +# TEST.13 - MERGED - Create, Attach, Deploy VRF+VRF LITE EXTENSION - +# one optional - Rest are populated from DCNM +- name: TEST.13 - MERGED - Create, Attach, Deploy VRF+VRF LITE EXTENSION - one optional (controller provided) cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -216,14 +253,14 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - - interface: "{{ ansible_int1 }}" # mandatory - - ip_address: "{{ ansible_switch1 }}" + - interface: "{{ interface_1 }}" # mandatory + - ip_address: "{{ switch_1 }}" deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.14 - MERGED - Query for vrfStatus transition to DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -243,11 +280,11 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' -- name: MERGED - Create, Attach and Deploy new VRF - Update with incorrect VRF ID. +- name: TEST.15 - MERGED - Create, Attach, Deploy VRF - Update with incorrect VRF ID. cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -257,8 +294,8 @@ vrf_template: Default_VRF_Universal vrf_extension_template: Default_VRF_Extension_Universal attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result ignore_errors: yes @@ -266,9 +303,9 @@ - assert: that: - 'result.changed == false' - - '"cant be updated to a different value" in result.msg' + - '"cannot be updated to a different value" in result.msg' -- name: MERGED - Create, Attach and Deploy new VRF - Update with Out of Range VRF ID. +- name: TEST.16 - MERGED - Create, Attach, Deploy VRF - Update with Out of Range VRF ID. cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -278,8 +315,8 @@ vrf_template: Default_VRF_Universal vrf_extension_template: Default_VRF_Extension_Universal attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result ignore_errors: yes @@ -289,7 +326,7 @@ - 'result.changed == false' - '"The item exceeds the allowed range of max" in result.msg' -- name: MERGED - Create, Attach and Deploy new VRF - Try configuring VRF LITE without required parameter +- name: TEST.17 - MERGED - Create, Attach, Deploy VRF - VRF LITE missing required parameter cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -300,8 +337,8 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional deploy: true @@ -313,7 +350,7 @@ - 'result.changed == false' - '"Invalid parameters in playbook: interface : Required parameter not found" in result.msg' -- name: MERGED - Create, Attach and Deploy new VRF - Try configuring VRF LITE to a non border switch +- name: TEST.18 - MERGED - Create, Attach and Deploy VRF - configure VRF LITE on non border switch cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -324,10 +361,10 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" + - ip_address: "{{ switch_3 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_1 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -346,7 +383,7 @@ ### CLEAN-UP ## ############################################### -- name: MERGED - Clean up any existing vrfs +- name: CLEANUP.1 - MERGED - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml index a4680f3e8..0b27e263d 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml @@ -36,12 +36,21 @@ that: - 'result.response.DATA != None' -- name: SETUP.2 - QUERY - Clean up any existing vrfs +- name: SETUP.2 - QUERY - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted + register: result_setup_2 -- name: SETUP.3 - QUERY - Create, Attach and Deploy new VRF - VLAN +- name: SETUP.2a - QUERY - Wait 40 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 40 + when: result_setup_2.changed == true + +- name: SETUP.4 - QUERY - Create, Attach and Deploy new VRF - VLAN cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -57,7 +66,7 @@ deploy: true register: result -- name: SETUP.4 - Query fabric state until vrfStatus transitions to DEPLOYED state +- name: SETUP.5 - Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -122,7 +131,7 @@ - 'result_1.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - 'result_1.response[0].attach[1].switchDetailsList[0].vlan == 500' -- name: TEST.2 - QUERY - Clean up existing vrfs +- name: TEST.2 - QUERY - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted @@ -146,7 +155,11 @@ - 'result_2.diff[0].attach[1].deploy == false' - 'result_2.diff[0].vrf_name == "ansible-vrf-int1"' -- name: TEST.3 - QUERY - Create, Attach and Deploy new VRF - VLAN +- name: TEST.2b - QUERY - Wait 40 seconds for controller and switch to sync + wait_for: + timeout: 40 + +- name: TEST.3 - QUERY - Create, Attach, Deploy VLAN+VRF cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -176,7 +189,7 @@ retries: 30 delay: 2 -- name: TEST.5 - QUERY - Create, Attach and Deploy new VRF - VRF/VRF LITE EXTENSION for one switch +- name: TEST.5 - QUERY - Create, Attach, Deploy VRF+LITE EXTENSION 1x switch cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: merged @@ -204,7 +217,7 @@ debug: var: result_4 -- name: TEST - Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.6 - QUERY - Query for vrfStatus transition to DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -240,7 +253,7 @@ - '"{{ switch_2 }}" in result_4.diff[0].attach[0].ip_address' - 'result_4.diff[0].vrf_name == "ansible-vrf-int2"' -- name: QUERY - Query the - VRF/VRF LITE EXTENSION Provided by the User in one switch +- name: TEST.7 - QUERY - Query the VRF+LITE EXTENSION in one switch cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -277,7 +290,7 @@ - 'result.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - 'result.response[0].attach[1].switchDetailsList[0].vlan == 1500' -- name: QUERY - Query without the config element +- name: TEST.8 - QUERY - Query without the config element cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -296,7 +309,7 @@ - 'result.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - 'result.response[0].attach[1].switchDetailsList[0].vlan == 1500' -- name: QUERY - Query the non available VRF +- name: TEST.9 - QUERY - Query the non available VRF cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: query @@ -321,7 +334,7 @@ ### CLEAN-UP ## ############################################### -- name: QUERY - Clean up any existing vrfs +- name: CLEANUP.1 - QUERY - Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ test_fabric }}" state: deleted From 72bfc10172e74776c802af31c20ebcba213af408 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sun, 8 Dec 2024 11:09:07 -1000 Subject: [PATCH 33/49] Update playbooks/roles 1. Add example dynamic inventory playbooks/roles/dynamic_inventory.py 2. Update dcnm_vrf/dcnm_tests.yaml with notes for dynamic_inventory.py 3. Add dcnm_vrf/dcnm_hosts.py --- playbooks/roles/dcnm_vrf/dcnm_hosts.yaml | 15 +++ playbooks/roles/dcnm_vrf/dcnm_tests.yaml | 39 ++++++ playbooks/roles/dynamic_inventory.py | 147 +++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 playbooks/roles/dcnm_vrf/dcnm_hosts.yaml create mode 100644 playbooks/roles/dcnm_vrf/dcnm_tests.yaml create mode 100755 playbooks/roles/dynamic_inventory.py diff --git a/playbooks/roles/dcnm_vrf/dcnm_hosts.yaml b/playbooks/roles/dcnm_vrf/dcnm_hosts.yaml new file mode 100644 index 000000000..109612797 --- /dev/null +++ b/playbooks/roles/dcnm_vrf/dcnm_hosts.yaml @@ -0,0 +1,15 @@ +all: + vars: + ansible_user: "admin" + ansible_password: "password-ndfc" + switch_password: "password-switch" + ansible_python_interpreter: python + ansible_httpapi_validate_certs: False + ansible_httpapi_use_ssl: True + children: + ndfc: + vars: + ansible_connection: ansible.netcommon.httpapi + ansible_network_os: cisco.dcnm.dcnm + hosts: + 192.168.1.1: diff --git a/playbooks/roles/dcnm_vrf/dcnm_tests.yaml b/playbooks/roles/dcnm_vrf/dcnm_tests.yaml new file mode 100644 index 000000000..87baf78a9 --- /dev/null +++ b/playbooks/roles/dcnm_vrf/dcnm_tests.yaml @@ -0,0 +1,39 @@ +--- +# This playbook can be used to execute integration tests for +# the roles located in: +# +# REPO_ROOT/tests/integration/targets/dcnm_vrf/tests/dcnm/*.yaml +# +# Either: +# 1. Modify the following: +# - The vars section below with details for your testing setup. +# - dcnm_hosts.yaml in this directory +# +# 2. Run the tests +# ansible-playbook dcnm_tests.yaml -i dcnm_hosts.yaml +# +# OR: +# 1. Modify ../dynamic_inventory.py to align with your setup +# This must contain the vars mentioned below and controller +# info from dcnm_hosts.yaml (modified for your setup) +# 2. Run the tests +# ansible-playbook dcnm_tests.yaml -i ../dynamic_inventory.py +# +# NOTES: +- hosts: dcnm + gather_facts: no + connection: ansible.netcommon.httpapi +# Uncomment and modify if not using dynamic_inventory.py +# vars: + # test_fabric: f1 + # switch_1: 10.1.1.2 + # switch_2: 10.1.1.3 + # switch_3: 10.1.1.4 + # interface_1: Ethernet1/2 + ## Uncomment ONE of the following testcases + # testcase: deleted + # testcase: merged + # testcase: query + + roles: + - dcnm_vrf diff --git a/playbooks/roles/dynamic_inventory.py b/playbooks/roles/dynamic_inventory.py new file mode 100755 index 000000000..c3a00dba4 --- /dev/null +++ b/playbooks/roles/dynamic_inventory.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import json +from os import environ + +""" +# Summary +Dynamic inventory for DCNM Collection integration tests. Inventory +is built from environment variables. + +# Usage + +See README.md in the top-level of this repository and define the environment +variables described there appropriately for your environment. +""" +test_fabric = environ.get("ND_FABRIC_NAME") +nd_ip4 = environ.get("ND_IP4") +nd_password = environ.get("ND_PASSWORD") +nd_testcase = environ.get("ND_TESTCASE") +nd_username = environ.get("ND_USERNAME", "admin") +nxos_password = environ.get("NXOS_PASSWORD") +nxos_username = environ.get("NXOS_USERNAME", "admin") +# These are not used in any integration tests +bgw_1 = environ.get("ND_BGW_1_IP4", "10.1.1.211") +bgw_2 = environ.get("ND_BGW_2_IP4", "10.1.1.212") +leaf_1 = environ.get("ND_LEAF_1_IP4", "10.1.1.106") +leaf_2 = environ.get("ND_LEAF_2_IP4", "10.1.1.107") +leaf_3 = environ.get("ND_LEAF_3_IP4", "10.1.1.108") +leaf_4 = environ.get("ND_LEAF_4_IP4", "10.1.1.109") +spine_1 = environ.get("ND_SPINE_1_IP4", "10.1.1.112") +spine_2 = environ.get("ND_SPINE_2_IP4", "10.1.1.113") +#----------------- +# dcnm_vrf +#----------------- +# switch_1: border switch role +switch_1 = environ.get("ND_SPINE_1_IP4", "10.1.1.112") +# switch_2: border switch role +switch_2 = environ.get("ND_SPINE_2_IP4", "10.1.1.113") +# switch_3: non-border switch role +switch_3 = environ.get("ND_LEAF_3_IP4", "10.1.1.108") +# Interface to use for VRF LITE extensions on switch_1, switch_2 +interface_1 = environ.get("ND_INTERFACE_1", "Ethernet1/2") +output = { + "_meta": {"hostvars": {}}, + "all": { + "children": ["ungrouped", "dcnm", "ndfc", "nxos"], + "vars": { + "ansible_httpapi_use_ssl": "true", + "ansible_httpapi_validate_certs": "false", + "ansible_password": nd_password, + "ansible_python_interpreter": "python", + "ansible_user": nd_username, + "bgw1": bgw_1, + "bgw2": bgw_2, + "leaf1": leaf_1, + "leaf2": leaf_2, + "leaf_1": leaf_1, + "leaf_2": leaf_2, + "leaf3": leaf_3, + "leaf4": leaf_4, + "nxos_username": nxos_username, + "nxos_password": nxos_password, + "switch_password": nxos_password, + "switch_username": nxos_username, + "spine1": spine_1, + "spine2": spine_2, + "switch1": switch_1, + "switch2": switch_2, + "switch_1": switch_1, + "switch_2": switch_2, + "switch_3": switch_3, + "interface_1": interface_1, + "testcase": nd_testcase, + "test_fabric": test_fabric + }, + }, + "dcnm": { + "hosts": [nd_ip4], + "vars": { + "ansible_connection": "ansible.netcommon.httpapi", + "ansible_network_os": "cisco.dcnm.dcnm", + }, + }, + "ndfc": { + "hosts": [nd_ip4], + "vars": { + "ansible_connection": "ansible.netcommon.httpapi", + "ansible_network_os": "cisco.dcnm.dcnm", + }, + }, + "nxos": { + "children": [ + "bgw1", + "bgw2", + "leaf_1", + "leaf_2", + "leaf1", + "leaf2", + "leaf3", + "leaf4", + "spine1", + "spine2", + "switch1", + "switch2", + ], + "vars": { + "ansible_become": "true", + "ansible_become_method": "enable", + "ansible_connection": "ansible.netcommon.httpapi", + "ansible_network_os": "cisco.nxos.nxos", + }, + }, + "bgw1": {"hosts": [bgw_1]}, + "bgw2": {"hosts": [bgw_2]}, + "leaf_1": {"hosts": [leaf_1]}, + "leaf_2": {"hosts": [leaf_2]}, + "leaf1": {"hosts": [leaf_1]}, + "leaf2": {"hosts": [leaf_2]}, + "leaf3": {"hosts": [leaf_3]}, + "leaf4": {"hosts": [leaf_4]}, + "spine1": {"hosts": [spine_1]}, + "spine2": {"hosts": [spine_2]}, + "switch1": {"hosts": [switch_1]}, + "switch2": {"hosts": [switch_2]}, +} + +print(json.dumps(output)) From 5749eda8d2976d363568425dbc50b9ecddec76c4 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sun, 8 Dec 2024 12:48:56 -1000 Subject: [PATCH 34/49] Move dynamic_inventory.py into playbooks/files Try to avoid sanity error (unexpected shebang) by moving dynamic_inventory.py out of playbooks/roles. --- playbooks/{roles => files}/dynamic_inventory.py | 0 playbooks/roles/dcnm_vrf/dcnm_tests.yaml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename playbooks/{roles => files}/dynamic_inventory.py (100%) diff --git a/playbooks/roles/dynamic_inventory.py b/playbooks/files/dynamic_inventory.py similarity index 100% rename from playbooks/roles/dynamic_inventory.py rename to playbooks/files/dynamic_inventory.py diff --git a/playbooks/roles/dcnm_vrf/dcnm_tests.yaml b/playbooks/roles/dcnm_vrf/dcnm_tests.yaml index 87baf78a9..3b6b3d24a 100644 --- a/playbooks/roles/dcnm_vrf/dcnm_tests.yaml +++ b/playbooks/roles/dcnm_vrf/dcnm_tests.yaml @@ -17,7 +17,7 @@ # This must contain the vars mentioned below and controller # info from dcnm_hosts.yaml (modified for your setup) # 2. Run the tests -# ansible-playbook dcnm_tests.yaml -i ../dynamic_inventory.py +# ansible-playbook dcnm_tests.yaml -i ../files/dynamic_inventory.py # # NOTES: - hosts: dcnm From acb3890a2d0e38a812937ba9fe3623175d9566ff Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sun, 8 Dec 2024 12:54:21 -1000 Subject: [PATCH 35/49] Appease ansible sanity According to the following link, '#!/usr/bin/env python' should be OK. https://docs.ansible.com/ansible/latest/dev_guide/testing/sanity/shebang.html Let's try... --- playbooks/files/dynamic_inventory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playbooks/files/dynamic_inventory.py b/playbooks/files/dynamic_inventory.py index c3a00dba4..60fc24b2a 100755 --- a/playbooks/files/dynamic_inventory.py +++ b/playbooks/files/dynamic_inventory.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # # Copyright (c) 2024 Cisco and/or its affiliates. # From 0bc26be2bf00d26424b92afaa918f30d43fc3f4d Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sun, 8 Dec 2024 13:04:25 -1000 Subject: [PATCH 36/49] Appease linters Fix pep8 E265: block comment should start with '# ' --- playbooks/files/dynamic_inventory.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/playbooks/files/dynamic_inventory.py b/playbooks/files/dynamic_inventory.py index 60fc24b2a..94523e3f6 100755 --- a/playbooks/files/dynamic_inventory.py +++ b/playbooks/files/dynamic_inventory.py @@ -25,6 +25,7 @@ """ # Summary + Dynamic inventory for DCNM Collection integration tests. Inventory is built from environment variables. @@ -33,13 +34,13 @@ See README.md in the top-level of this repository and define the environment variables described there appropriately for your environment. """ -test_fabric = environ.get("ND_FABRIC_NAME") nd_ip4 = environ.get("ND_IP4") nd_password = environ.get("ND_PASSWORD") nd_testcase = environ.get("ND_TESTCASE") nd_username = environ.get("ND_USERNAME", "admin") nxos_password = environ.get("NXOS_PASSWORD") nxos_username = environ.get("NXOS_USERNAME", "admin") + # These are not used in any integration tests bgw_1 = environ.get("ND_BGW_1_IP4", "10.1.1.211") bgw_2 = environ.get("ND_BGW_2_IP4", "10.1.1.212") @@ -49,9 +50,13 @@ leaf_4 = environ.get("ND_LEAF_4_IP4", "10.1.1.109") spine_1 = environ.get("ND_SPINE_1_IP4", "10.1.1.112") spine_2 = environ.get("ND_SPINE_2_IP4", "10.1.1.113") -#----------------- + +# ----------------- # dcnm_vrf -#----------------- +# ----------------- + +# VXLAN/EVPN Fabric Name +test_fabric = environ.get("ND_FABRIC_NAME") # switch_1: border switch role switch_1 = environ.get("ND_SPINE_1_IP4", "10.1.1.112") # switch_2: border switch role @@ -60,6 +65,11 @@ switch_3 = environ.get("ND_LEAF_3_IP4", "10.1.1.108") # Interface to use for VRF LITE extensions on switch_1, switch_2 interface_1 = environ.get("ND_INTERFACE_1", "Ethernet1/2") + +# output is printed to STDOUT, where ansible-playbook -i reads it. +# If you change any vars above, be sure to add them below. +# We'll clean this up as the integration test vars are standardized. + output = { "_meta": {"hostvars": {}}, "all": { From b78e99649140a51adc344347840b7c83de6d63fd Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 9 Dec 2024 20:05:28 -1000 Subject: [PATCH 37/49] dcnm_vrf: Updates to integration tests 1. Standardize integration test var names fabric_1 switch_1 switch_2 switch_3 interface_1 interface_2 interface_3 2. All tests - SETUP. Add task to print all vars - Standardize task titles to include ansible state 3 overridden.yaml - Add a workaround for issue seen with ND 3.2.1e In step TEST.6, NDFC issues an NX-OS CLI that immediately switches from from configure profile mode, to configure terminal; vrf context . This command results in FAILURE (switch accounting log). Adding a wait_for will not help since this all happens within step TEST.6. A config-deploy resolves the OUT-OF-SYNC VRF status. - Add REQUIRED VARS section 4. query.yaml - Update REQUIRED VARS section 5. merged.yaml - Add missing wait_for after VRF deletion - Update REQUIRED VARS section - Renumber tests to group sub-tests 6. deleted.yaml - Update REQUIRED VARS section 7. dynamic_inventory.py - Add conditional blocks to set vars based on role --- playbooks/files/dynamic_inventory.py | 79 +++- .../targets/dcnm_vrf/tests/dcnm/deleted.yaml | 62 ++-- .../targets/dcnm_vrf/tests/dcnm/merged.yaml | 350 +++++++++++------- .../dcnm_vrf/tests/dcnm/overridden.yaml | 220 +++++++---- .../targets/dcnm_vrf/tests/dcnm/query.yaml | 128 +++---- 5 files changed, 526 insertions(+), 313 deletions(-) diff --git a/playbooks/files/dynamic_inventory.py b/playbooks/files/dynamic_inventory.py index 94523e3f6..4a5a45ef8 100755 --- a/playbooks/files/dynamic_inventory.py +++ b/playbooks/files/dynamic_inventory.py @@ -26,22 +26,25 @@ """ # Summary -Dynamic inventory for DCNM Collection integration tests. Inventory -is built from environment variables. +Dynamic inventory for DCNM Collection integration tests. +Inventory is built from environment variables. # Usage See README.md in the top-level of this repository and define the environment variables described there appropriately for your environment. """ +nd_role = environ.get("ND_ROLE", "dcnm_vrf") +nd_testcase = environ.get("ND_TESTCASE", "query") + +fabric_1 = environ.get("ND_FABRIC_1") nd_ip4 = environ.get("ND_IP4") nd_password = environ.get("ND_PASSWORD") -nd_testcase = environ.get("ND_TESTCASE") nd_username = environ.get("ND_USERNAME", "admin") nxos_password = environ.get("NXOS_PASSWORD") nxos_username = environ.get("NXOS_USERNAME", "admin") -# These are not used in any integration tests +# Base set of switches bgw_1 = environ.get("ND_BGW_1_IP4", "10.1.1.211") bgw_2 = environ.get("ND_BGW_2_IP4", "10.1.1.212") leaf_1 = environ.get("ND_LEAF_1_IP4", "10.1.1.106") @@ -51,20 +54,56 @@ spine_1 = environ.get("ND_SPINE_1_IP4", "10.1.1.112") spine_2 = environ.get("ND_SPINE_2_IP4", "10.1.1.113") -# ----------------- -# dcnm_vrf -# ----------------- +# Base set of interfaces +interface_1 = environ.get("ND_INTERFACE_1", "Ethernet1/1") +interface_2 = environ.get("ND_INTERFACE_2", "Ethernet1/2") +interface_3 = environ.get("ND_INTERFACE_3", "Ethernet1/3") -# VXLAN/EVPN Fabric Name -test_fabric = environ.get("ND_FABRIC_NAME") -# switch_1: border switch role -switch_1 = environ.get("ND_SPINE_1_IP4", "10.1.1.112") -# switch_2: border switch role -switch_2 = environ.get("ND_SPINE_2_IP4", "10.1.1.113") -# switch_3: non-border switch role -switch_3 = environ.get("ND_LEAF_3_IP4", "10.1.1.108") -# Interface to use for VRF LITE extensions on switch_1, switch_2 -interface_1 = environ.get("ND_INTERFACE_1", "Ethernet1/2") +if nd_role == "dcnm_vrf": + # VXLAN/EVPN Fabric Name + # fabric_1 + # - all tests + # switch_1 + # - all tests + # - vrf capable + switch_1 = spine_1 + # switch_2 + # - all tests + # - vrf-lite capable + switch_2 = spine_2 + # switch_3 + # - merged + # - NOT vrf-lite capable + switch_3 = leaf_3 + # interface_1 + # - no tests + # interface_2 + # - all tests + # - switch_2 VRF LITE extensions + # interface_3 + # - merged + # - switch_3 non-vrf-lite capable switch + # - overridden + # - Removed from test due to unrelated IP POOL errors. + # - It appears that fabric would need to have SUBNET + # resource added? + # +elif nd_role == "vrf_lite": + # VXLAN/EVPN Fabric Name + # Uses fabric_1 + # switch_1: vrf-lite capable + switch_1 = spine_1 + # switch_2: vrf-lite capable + switch_2 = spine_2 + # switch_3: vrf-lite capable + switch_3 = bgw_1 + interface_1 = interface_1 + interface_2 = interface_2 + interface_3 = interface_3 +else: + switch_1 = leaf_1 + switch_2 = spine_1 + switch_3 = bgw_1 # output is printed to STDOUT, where ansible-playbook -i reads it. # If you change any vars above, be sure to add them below. @@ -80,6 +119,7 @@ "ansible_password": nd_password, "ansible_python_interpreter": "python", "ansible_user": nd_username, + "fabric_1": fabric_1, "bgw1": bgw_1, "bgw2": bgw_2, "leaf1": leaf_1, @@ -100,8 +140,9 @@ "switch_2": switch_2, "switch_3": switch_3, "interface_1": interface_1, - "testcase": nd_testcase, - "test_fabric": test_fabric + "interface_2": interface_2, + "interface_3": interface_3, + "testcase": nd_testcase }, }, "dcnm": { diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml index 9c7269853..8ed6c01dd 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml @@ -1,31 +1,43 @@ ############################################## ## REQUIRED VARS ## ############################################## -# test_fabric +# fabric_1 # A VXLAN_EVPN fabric # # switch_1 -# A border switch +# A vrf-lite capable switch +# Does not require an interface # # switch_2 -# A border switch +# A vrf-lite capable switch # -# interface_1 -# Ethernet interface on switch_1 and switch_2 +# interface_2 +# Ethernet interface on switch_2 # used to test VRF LITE configuration. +# +############################################## ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" +- name: SETUP.0 - DELETED - print vars + ansible.builtin.debug: + var: item + with_items: + - "fabric_1 : {{ fabric_1 }}" + - "switch_1 : {{ switch_1 }}" + - "switch_2 : {{ switch_2 }}" + - "interface_2 : {{ interface_2 }}" + - name: SETUP.1 - DELETED - Verify fabric is deployed. cisco.dcnm.dcnm_rest: method: GET @@ -38,7 +50,7 @@ - name: SETUP.2 - DELETED - Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted register: result_setup_2 @@ -49,7 +61,7 @@ - name: SETUP.3 - DELETED - Create, Attach and Deploy VLAN+VRF cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -63,9 +75,9 @@ deploy: true register: result -- name: SETUP.4 - DELETED - Query for vrfStatus transition to DEPLOYED +- name: SETUP.4 - DELETED - Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -93,7 +105,7 @@ - name: TEST.1 - DELETED - Delete VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted config: - vrf_name: ansible-vrf-int1 @@ -132,7 +144,7 @@ - name: TEST.3 - DELETED - Create, Attach and Deploy new VLAN+VRF+LITE EXTENSION 1x switch cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -145,7 +157,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_1 }}" # mandatory + interface: "{{ interface_2 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -154,9 +166,9 @@ deploy: true register: result -- name: TEST.4 - DELETED - Query for vrfStatus transition to DEPLOYED +- name: TEST.4 - DELETED - Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -180,7 +192,7 @@ - name: TEST.5 - DELETED - Delete VRF+LITE EXTENSION 1x switch cisco.dcnm.dcnm_vrf: &conf1 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted config: - vrf_name: ansible-vrf-int1 @@ -193,7 +205,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_1 }}" # mandatory + interface: "{{ interface_2 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -233,9 +245,9 @@ - 'result.response|length == 0' - 'result.diff|length == 0' -- name: TEST.7 - DELETED - Create, Attach, Deploy VRF+LITE EXTENSION 1x switch +- name: TEST.7 - DELETED - Create, Attach, Deploy VRF+LITE EXTENSION switch_2 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -248,7 +260,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_1 }}" # mandatory + interface: "{{ interface_2 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -257,9 +269,9 @@ deploy: true register: result -- name: TEST.8 - DELETED - Query for vrfStatus transition to DEPLOYED +- name: TEST.8 - DELETED - Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -283,7 +295,7 @@ - name: TEST.9 - DELETED - Delete VRF+LITE EXTENSION - empty config element cisco.dcnm.dcnm_vrf: &conf2 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted config: register: result @@ -325,5 +337,5 @@ - name: CLEANUP.1 - DELETED - Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml index 0911b7282..6a42fd841 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml @@ -1,35 +1,53 @@ ############################################## ## REQUIRED VARS ## ############################################## -# test_fabric +# fabric_1 # A VXLAN_EVPN fabric # # switch_1 -# A border switch +# A vrf-lite capable switch +# Does not require an interface. # # switch_2 -# A border switch +# A vrf-lite capable switch # # switch_3 -# A non-border switch +# A NON-vrf-lite capable switch # -# interface_1 -# Ethernet interface on switch_1 and switch_2 -# used to test VRF LITE configuration. +# interface_2 +# Ethernet interface on switch_2 +# Used to test VRF LITE configuration. +# +# interface_3 +# Ethernet interface on switch_3 +# Used to verify error when applied VRF LITE +# extension on a non-vrf-lite capable switch. +############################################## ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" -- name: SETUP.1 - MERGED - Verify fabric is deployed. +- name: SETUP.0 - MERGED - print vars + ansible.builtin.debug: + var: item + with_items: + - "fabric_1 : {{ fabric_1 }}" + - "switch_1 : {{ switch_1 }}" + - "switch_2 : {{ switch_2 }}" + - "switch_3 : {{ switch_3 }}" + - "interface_2 : {{ interface_2 }}" + - "interface_3 : {{ interface_3 }}" + +- name: SETUP.1 - MERGED - [dcnm_rest.GET] Verify fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" @@ -39,27 +57,27 @@ that: - 'result.response.DATA != None' -- name: SETUP.2 - MERGED - Delete all VRFs +- name: SETUP.2 - MERGED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted register: result_setup_2 -- name: SETUP.3 - MERGED - Wait 40 seconds for controller and switch to sync +- name: SETUP.2a - MERGED - [wait_for] Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: - timeout: 40 + timeout: 60 when: result_setup_2.changed == true ############################################### ### MERGED ## ############################################### -- name: TEST.1 - MERGED - Create, Attach, Deploy VLAN+VRF - cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" +- name: TEST.1 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 + cisco.dcnm.dcnm_vrf: &conf1 + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -71,49 +89,62 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_1 + +- name: TEST.1a - MERGED - [debug] print result_1 + ansible.builtin.debug: + var: result_1 -- name: TEST.2 - MERGED - Query for vrfStatus transition to DEPLOYED +- name: TEST.1b - MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_1b until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_1b.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.3 - MERGED - conf1 - Idempotence - cisco.dcnm.dcnm_vrf: *conf - register: result + - 'result_1.changed == true' + - 'result_1.response[0].RETURN_CODE == 200' + - 'result_1.response[1].RETURN_CODE == 200' + - 'result_1.response[2].RETURN_CODE == 200' + - '(result_1.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_1.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_1.diff[0].attach[0].deploy == true' + - 'result_1.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_1.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_1.diff[0].attach[1].ip_address' + - 'result_1.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.1c - MERGED - [merged] conf1 - Idempotence + cisco.dcnm.dcnm_vrf: *conf1 + register: result_1c - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_1c.changed == false' + - 'result_1c.response|length == 0' -- name: TEST.4 - MERGED - Delete all VRFs +- name: TEST.1d - MERGED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted + register: result_1d -- name: TEST.5 - MERGED - Create, Attach, Deploy VLAN+VRF (controller provided) +- name: TEST.1e - MERGED - [wait_for] Wait 60 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 60 + when: result_1d.changed == true + +- name: TEST.2 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF (controller provided) cisco.dcnm.dcnm_vrf: &conf2 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -124,55 +155,63 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_2 + +- name: TEST.2a - MERGED - [debug] print result_2 + ansible.builtin.debug: + var: result_2 -- name: TEST.6 - MERGED - Query for vrfStatus transition to DEPLOYED +- name: TEST.2b - MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_2b until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_2b.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.7 - MERGED - conf2 - Idempotence + - 'result_2.changed == true' + - 'result_2.response[0].RETURN_CODE == 200' + - 'result_2.response[1].RETURN_CODE == 200' + - 'result_2.response[2].RETURN_CODE == 200' + - '(result_2.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2.diff[0].attach[0].deploy == true' + - 'result_2.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_2.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_2.diff[0].attach[1].ip_address' + - 'result_2.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.2c - MERGED - [merged] conf2 - Idempotence cisco.dcnm.dcnm_vrf: *conf2 - register: result + register: result_2c + +- name: TEST.2d - MERGED - [debug] print result_2c + ansible.builtin.debug: + var: result_2c - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_2c.changed == false' + - 'result_2c.response|length == 0' -- name: TEST.8 - MERGED - Delete all VRFs +- name: TEST.2e - MERGED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted -- name: TEST.8a - MERGED - Wait 40 seconds for controller and switch to sync +- name: TEST.2f - MERGED - [wait_for] Wait 60 seconds for controller and switch to sync # While vrf-lite extension was not configured above, we still hit VRF # OUT-OF-SYNC. Let's see if waiting helps here too. wait_for: - timeout: 40 + timeout: 60 -- name: TEST.9 - MERGED - Create, Attach, Deploy VLAN+VRF+LITE EXTENSION 1x switch (user provided) +- name: TEST.3 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF+LITE EXTENSION ansible-vrf-int1 switch_2 (user provided) cisco.dcnm.dcnm_vrf: &conf3 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -185,66 +224,71 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_1 }}" # mandatory + interface: "{{ interface_2 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true - register: result + register: result_3 -- name: TEST.10 - MERGED - Query for vrfStatus transition to DEPLOYED +- name: TEST.3a - MERGED - [debug] print result_3 + ansible.builtin.debug: + var: result_3 + +- name: TEST.3b - MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_3b until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_3b.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.11 - MERGED - conf3 - Idempotence + - 'result_3.changed == true' + - 'result_3.response[0].RETURN_CODE == 200' + - 'result_3.response[1].RETURN_CODE == 200' + - 'result_3.response[2].RETURN_CODE == 200' + - '(result_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3.diff[0].attach[0].deploy == true' + - 'result_3.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' + - 'result_3.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.3b - MERGED - [merged] conf3 - Idempotence cisco.dcnm.dcnm_vrf: *conf3 - register: result + register: result_3b + +- name: TEST.3c - MERGED - [debug] print result_3b + ansible.builtin.debug: + var: result_3b - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_3b.changed == false' + - 'result_3b.response|length == 0' -- name: TEST.12 - MERGED - Delete all VRFs +- name: TEST.3d - MERGED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted -- name: TEST.12a - MERGED - Wait 40 seconds for controller and switch to sync +- name: TEST.3e - MERGED - Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: - timeout: 40 + timeout: 60 -# Original wording in case I misinterpreted -# TEST.13 - MERGED - Create, Attach, Deploy VRF+VRF LITE EXTENSION - -# one optional - Rest are populated from DCNM -- name: TEST.13 - MERGED - Create, Attach, Deploy VRF+VRF LITE EXTENSION - one optional (controller provided) +- name: TEST.4 - MERGED - [merged] Create, Attach, Deploy VRF+VRF LITE EXTENSION - one optional (controller provided) cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -253,40 +297,44 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: + - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" vrf_lite: - - interface: "{{ interface_1 }}" # mandatory - - ip_address: "{{ switch_1 }}" + - interface: "{{ interface_2 }}" # mandatory deploy: true - register: result + register: result_4 -- name: TEST.14 - MERGED - Query for vrfStatus transition to DEPLOYED +- name: TEST.4a - MERGED - [debug] print result_4 + ansible.builtin.debug: + var: result_4 + +- name: TEST.4a - MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_4a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_4a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_1 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.15 - MERGED - Create, Attach, Deploy VRF - Update with incorrect VRF ID. + - 'result_4.changed == true' + - 'result_4.response[0].RETURN_CODE == 200' + - 'result_4.response[1].RETURN_CODE == 200' + - 'result_4.response[2].RETURN_CODE == 200' + - '(result_4.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_4.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_4.diff[0].attach[0].deploy == true' + - 'result_4.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_4.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_4.diff[0].attach[1].ip_address' + - 'result_4.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.5 - MERGED - [merged] Create, Attach, Deploy VRF - Update with incorrect VRF ID. cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -297,17 +345,21 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_5 ignore_errors: yes +- name: TEST.5a - MERGED - [debug] print result_5 + ansible.builtin.debug: + var: result_5 + - assert: that: - - 'result.changed == false' - - '"cannot be updated to a different value" in result.msg' + - 'result_5.changed == false' + - '"cannot be updated to a different value" in result_5.msg' -- name: TEST.16 - MERGED - Create, Attach, Deploy VRF - Update with Out of Range VRF ID. +- name: TEST.6 - MERGED - [merged] Create, Attach, Deploy VRF - Update with Out of Range VRF ID. cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -318,17 +370,21 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_6 ignore_errors: yes +- name: TEST.6a - MERGED - [debug] print result_6 + ansible.builtin.debug: + var: result_6 + - assert: that: - - 'result.changed == false' - - '"The item exceeds the allowed range of max" in result.msg' + - 'result_6.changed == false' + - '"The item exceeds the allowed range of max" in result_6.msg' -- name: TEST.17 - MERGED - Create, Attach, Deploy VRF - VRF LITE missing required parameter +- name: TEST.7 - MERGED - [merged] Create, Attach, Deploy VRF - VRF LITE missing required parameter cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -342,17 +398,21 @@ vrf_lite: - peer_vrf: ansible-vrf-int1 # optional deploy: true - register: result + register: result_7 ignore_errors: yes +- name: TEST.7a - MERGED - [debug] print result_7 + ansible.builtin.debug: + var: result_7 + - assert: that: - - 'result.changed == false' - - '"Invalid parameters in playbook: interface : Required parameter not found" in result.msg' + - 'result_7.changed == false' + - '"Invalid parameters in playbook: interface : Required parameter not found" in result_7.msg' -- name: TEST.18 - MERGED - Create, Attach and Deploy VRF - configure VRF LITE on non border switch +- name: TEST.8 - MERGED - [merged] Create, Attach and Deploy VRF - configure VRF LITE on non border switch cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -364,26 +424,30 @@ - ip_address: "{{ switch_3 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_1 }}" # mandatory + interface: "{{ interface_3 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true - register: result + register: result_8 ignore_errors: yes +- name: TEST.8a - MERGED - [debug] print result_8 + ansible.builtin.debug: + var: result_8 + - assert: that: - - 'result.changed == false' - - '"VRF LITE cannot be attached to switch" in result.msg' + - 'result_8.changed == false' + - '"VRF LITE cannot be attached to switch" in result_8.msg' ############################################### ### CLEAN-UP ## ############################################### -- name: CLEANUP.1 - MERGED - Delete all VRFs +- name: CLEANUP.1 - MERGED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml index 1cee0dca7..d1212b002 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml @@ -1,16 +1,55 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# A VXLAN_EVPN fabric +# +# switch_1 +# A vrf-lite capable switch +# +# switch_2 +# A vrf-lite capable switch +# +# interface_2 +# Ethernet interface on switch_2 +# Used to test VRF LITE configuration. +# +############################################## + +############################################## +## OTHER REQUIREMENTS ## +############################################## +# +# 1. A SUBNET pool for 10.33.0.0/24 must be +# allocated for the fabric_1. +# THIS REQUIREMENT HAS BEEN TEMPORARILY +# REMOVED BY NOT CHANGING THE ipv4_addr and +# neighbor_ipv4 values in the VRF LITE payload. +# TODO: Discuss with Mike. +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" -- name: OVERRIDDEN - Verify if fabric is deployed. +- name: SETUP.0 - OVERRIDDEN - print vars + ansible.builtin.debug: + var: item + with_items: + - "fabric_1 : {{ fabric_1 }}" + - "switch_1 : {{ switch_1 }}" + - "switch_2 : {{ switch_2 }}" + - "interface_2 : {{ interface_2 }}" + +- name: SETUP.1 - OVERRIDDEN - [dcnn_rest.GET] Verify if fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" @@ -20,14 +59,23 @@ that: - 'result.response.DATA != None' -- name: OVERRIDDEN - Clean up any existing vrfs +- name: SETUP.2 - OVERRIDDEN - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted + register: result_setup_2 -- name: OVERRIDDEN - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: SETUP.2a - OVERRIDDEN - [wait_for] Wait 40 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 40 + when: result_setup_2.changed == true + +- name: SETUP.3 OVERRIDDEN - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -36,14 +84,14 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: SETUP.4 OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -61,17 +109,17 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' ############################################### ### OVERRIDDEN ## ############################################### -- name: OVERRIDDEN - Update existing VRF using overridden - delete and create - cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" +- name: TEST.1 - OVERRIDDEN - [overridden] Override existing VRF ansible-vrf-int1 to create new VRF ansible-vrf-int2 + cisco.dcnm.dcnm_vrf: &conf1 + fabric: "{{ fabric_1 }}" state: overridden config: - vrf_name: ansible-vrf-int2 @@ -80,14 +128,14 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.2 - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -115,8 +163,8 @@ - 'result.diff[1].attach[1].deploy == false' - 'result.diff[1].vrf_name == "ansible-vrf-int1"' -- name: OVERRIDDEN - conf - Idempotence - cisco.dcnm.dcnm_vrf: *conf +- name: TEST.3 - OVERRIDDEN - [overridden] conf1 - Idempotence + cisco.dcnm.dcnm_vrf: *conf1 register: result - assert: @@ -124,14 +172,20 @@ - 'result.changed == false' - 'result.response|length == 0' -- name: OVERRIDDEN - Clean up any existing vrfs +- name: TEST.4 - OVERRIDDEN - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted + register: result_4 + +- name: TEST.4a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 + when: result_4.changed == true -- name: OVERRIDDEN - Create, Attach and Deploy new VRF - VRF/VRF LITE EXTENSION Provided by the User in one switch +- name: TEST.5 - OVERRIDDEN - [merged] ansible-vrf-int2 to add vrf_lite extension to switch_2 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int2 @@ -140,22 +194,30 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 1500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # optional - interface: "{{ ansible_int1 }}" # mandatory - ipv4_addr: 10.33.0.2/24 # optional - neighbor_ipv4: 10.33.0.1 # optional + interface: "{{ interface_2 }}" # mandatory + ipv4_addr: 10.33.0.1/24 # optional + neighbor_ipv4: 10.33.0.0 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.5a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 60 + when: result.changed == true + +- name: TEST.5b - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -173,13 +235,13 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int2"' -- name: OVERRIDDEN - Update existing VRF LITE using overridden - VRF stays Whereas Ext is modified/overridden - cisco.dcnm.dcnm_vrf: &conf1 - fabric: "{{ test_fabric }}" +- name: TEST.6 - OVERRIDDEN - [overridden] Override vrf_lite extension with new dot1q value + cisco.dcnm.dcnm_vrf: &conf2 + fabric: "{{ fabric_1 }}" state: overridden config: - vrf_name: ansible-vrf-int2 @@ -188,22 +250,46 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 1500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # optional - interface: "{{ ansible_int1 }}" # mandatory - ipv4_addr: 10.33.0.6/24 # optional - neighbor_ipv4: 10.33.0.1 # optional - ipv6_addr: 2010::10:34:0:10/64 # optional - neighbor_ipv6: 2010::10:34:0:7 # optional + interface: "{{ interface_2 }}" # mandatory + ipv4_addr: 10.33.0.1/24 # optional + neighbor_ipv4: 10.33.0.0 # optional + ipv6_addr: 2010::10:34:0:7/64 # optional + neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 21 # dot1q can be got from dcnm deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.6a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 60 + when: result.changed == true + +# In step TEST.6, NDFC issues an NX-OS CLI that immediately switches from +# from configure profile mode, to configure terminal; vrf context . +# This command results in FAILURE (switch accounting log). Adding a +# wait_for will not help since this all happens within step TEST.6. +# A config-deploy resolves the OUT-OF-SYNC VRF status. +- name: TEST.6b - OVERRIDDEN - [dcnm_rest.POST] - config-deploy to workaround FAILED/OUT-OF-SYNC VRF status + cisco.dcnm.dcnm_rest: + method: POST + path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}/config-deploy?forceShowRun=false" + when: result.changed == true + +- name: TEST.6c - OVERRIDDEN - [wait_for] Wait 60 for vrfStatus == DEPLOYED + wait_for: + timeout: 60 + when: result.changed == true + +- name: TEST.6d - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -220,8 +306,8 @@ - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].vrf_name == "ansible-vrf-int2"' -- name: OVERRIDDEN - conf - Idempotence - cisco.dcnm.dcnm_vrf: *conf1 +- name: TEST.7 - OVERRIDDEN - [overridden] conf2 - Idempotence + cisco.dcnm.dcnm_vrf: *conf2 register: result - assert: @@ -229,9 +315,9 @@ - 'result.changed == false' - 'result.response|length == 0' -- name: OVERRIDDEN - Update existing VRF LITE using overridden - VRF modified and Ext is modified - Old ones deleted - cisco.dcnm.dcnm_vrf: &conf2 - fabric: "{{ test_fabric }}" +- name: TEST.8 - OVERRIDDEN - [overridden] Override ansible-vrf-int2 to create ansible-vrf-int1 with LITE Extension + cisco.dcnm.dcnm_vrf: &conf3 + fabric: "{{ fabric_1 }}" state: overridden config: - vrf_name: ansible-vrf-int1 @@ -240,22 +326,30 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory - ipv4_addr: 10.33.0.3/24 # optional - neighbor_ipv4: 10.33.0.1 # optional + interface: "{{ interface_2 }}" # mandatory + ipv4_addr: 10.33.0.1/24 # optional + neighbor_ipv4: 10.33.0.0 # optional ipv6_addr: 2010::10:34:0:1/64 # optional neighbor_ipv6: 2010::10:34:0:2 # optional dot1q: 31 # dot1q can be got from dcnm deploy: true register: result -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.8a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync + # The vrf lite profile removal returns ok for deployment, but the switch + # takes time to remove the profile so wait for some time before creating + # a new vrf, else the switch goes into OUT-OF-SYNC state + wait_for: + timeout: 60 + when: result.changed == true + +- name: TEST.8b - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -284,8 +378,8 @@ - 'result.diff[1].attach[1].deploy == false' - 'result.diff[1].vrf_name == "ansible-vrf-int2"' -- name: OVERRIDDEN - conf - Idempotence - cisco.dcnm.dcnm_vrf: *conf2 +- name: TEST.9 - OVERRIDDEN - [overridden] conf3 - Idempotence + cisco.dcnm.dcnm_vrf: *conf3 register: result - assert: @@ -297,7 +391,7 @@ ## CLEAN-UP ## ############################################## -- name: OVERRIDDEN - Clean up any existing vrfs +- name: CLEANUP.1 - OVERRIDDEN - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml index 0b27e263d..d654f2c5e 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml @@ -1,32 +1,42 @@ ############################################## ## REQUIRED VARS ## ############################################## -# test_fabric +# fabric_1 # A VXLAN_EVPN fabric # # switch_1 -# A border switch +# A vrf-lite capable switch # # switch_2 -# A border switch +# A vrf-lite capable switch # -# interface_1 -# Ethernet interface on switch_1 and switch_2 -# used to test VRF LITE configuration. +# interface_2 +# Ethernet interface on switch_2 +# Used to test VRF LITE configuration. +############################################## ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" -- name: SETUP.1 - QUERY - Verify if fabric is deployed. +- name: SETUP.0 - QUERY - print vars + ansible.builtin.debug: + var: item + with_items: + - "fabric_1 : {{ fabric_1 }}" + - "switch_1 : {{ switch_1 }}" + - "switch_2 : {{ switch_2 }}" + - "interface_2 : {{ interface_2 }}" + +- name: SETUP.1 - QUERY - [dcnm_rest.GET] Verify if fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" @@ -36,9 +46,9 @@ that: - 'result.response.DATA != None' -- name: SETUP.2 - QUERY - Delete all VRFs +- name: SETUP.2 - QUERY - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted register: result_setup_2 @@ -50,9 +60,9 @@ timeout: 40 when: result_setup_2.changed == true -- name: SETUP.4 - QUERY - Create, Attach and Deploy new VRF - VLAN +- name: SETUP.4 - QUERY - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -66,9 +76,9 @@ deploy: true register: result -- name: SETUP.5 - Query fabric state until vrfStatus transitions to DEPLOYED state +- name: SETUP.5 - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -76,10 +86,6 @@ retries: 30 delay: 2 -- name: debug result var SETUP.4 - debug: - var: result - - assert: that: - 'result.changed == true' @@ -98,9 +104,9 @@ # ### QUERY ## # ############################################### -- name: TEST.1 - QUERY - Query the VRF +- name: TEST.1 - QUERY - [query] Query VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query config: - vrf_name: ansible-vrf-int1 @@ -114,7 +120,7 @@ deploy: true register: result_1 -- name: debug result_1 TEST.1 +- name: TEST.1b print result_1 debug: var: result_1 @@ -131,13 +137,13 @@ - 'result_1.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - 'result_1.response[0].attach[1].switchDetailsList[0].vlan == 500' -- name: TEST.2 - QUERY - Delete all VRFs +- name: TEST.2 - QUERY - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted register: result_2 -- name: debug result_2 TEST.2 +- name: TEST.2a print result_2 debug: var: result_2 @@ -155,13 +161,13 @@ - 'result_2.diff[0].attach[1].deploy == false' - 'result_2.diff[0].vrf_name == "ansible-vrf-int1"' -- name: TEST.2b - QUERY - Wait 40 seconds for controller and switch to sync +- name: TEST.2b - QUERY - [wait_for] Wait 60 seconds for controller and switch to sync wait_for: - timeout: 40 + timeout: 60 -- name: TEST.3 - QUERY - Create, Attach, Deploy VLAN+VRF +- name: TEST.3 - QUERY - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int2 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int2 @@ -175,13 +181,13 @@ deploy: true register: result_3 -- name: debug result_3 TEST.3 +- name: TEST.3a print result_3 debug: var: result_3 -- name: TEST.4 - Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.4 - QUERY - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -189,9 +195,21 @@ retries: 30 delay: 2 -- name: TEST.5 - QUERY - Create, Attach, Deploy VRF+LITE EXTENSION 1x switch +- assert: + that: + - 'result_3.changed == true' + - 'result_3.response[0].RETURN_CODE == 200' + - 'result_3.response[1].RETURN_CODE == 200' + - 'result_3.response[2].RETURN_CODE == 200' + - '(result_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3.diff[0].attach[0].deploy == true' + - '"{{ switch_1 }}" in result_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' + +- name: TEST.5 - QUERY - [merged] Create, Attach, Deploy VRF+LITE EXTENSION ansible-vrf-int2 on switch_2 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int2 @@ -204,7 +222,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # peer_vrf is mandatory - interface: "{{ interface_1 }}" # mandatory + interface: "{{ interface_2 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -213,13 +231,13 @@ deploy: true register: result_4 -- name: debug result_4 TEST.5 (before query) +- name: TEST.5a print result_4 debug: var: result_4 -- name: TEST.6 - QUERY - Query for vrfStatus transition to DEPLOYED +- name: TEST.6 - QUERY - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -227,22 +245,6 @@ retries: 30 delay: 2 -- name: debug result_4 TEST.5 (after query) - debug: - var: result_4 - -- assert: - that: - - 'result_3.changed == true' - - 'result_3.response[0].RETURN_CODE == 200' - - 'result_3.response[1].RETURN_CODE == 200' - - 'result_3.response[2].RETURN_CODE == 200' - - '(result_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result_3.diff[0].attach[0].deploy == true' - - '"{{ switch_1 }}" in result_3.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' - - assert: that: - 'result_4.changed == true' @@ -253,9 +255,9 @@ - '"{{ switch_2 }}" in result_4.diff[0].attach[0].ip_address' - 'result_4.diff[0].vrf_name == "ansible-vrf-int2"' -- name: TEST.7 - QUERY - Query the VRF+LITE EXTENSION in one switch +- name: TEST.7 - QUERY - [query] Query VRF+LITE EXTENSION ansible-vrf-int2 switch_2 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query config: - vrf_name: ansible-vrf-int2 @@ -268,7 +270,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # peer_vrf is mandatory - interface: "{{ interface_1 }}" # mandatory + interface: "{{ interface_2 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -290,9 +292,9 @@ - 'result.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - 'result.response[0].attach[1].switchDetailsList[0].vlan == 1500' -- name: TEST.8 - QUERY - Query without the config element +- name: TEST.8 - QUERY - [query] Query without the config element cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: result @@ -309,9 +311,9 @@ - 'result.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - 'result.response[0].attach[1].switchDetailsList[0].vlan == 1500' -- name: TEST.9 - QUERY - Query the non available VRF +- name: TEST.9 - QUERY - [query] Query non-existent VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query config: - vrf_name: ansible-vrf-int1 @@ -334,7 +336,7 @@ ### CLEAN-UP ## ############################################### -- name: CLEANUP.1 - QUERY - Delete all VRFs +- name: CLEANUP.1 - QUERY - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted From ca7bea830584b3f136f1c5c302131ec22ed60266 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 10 Dec 2024 09:04:30 -1000 Subject: [PATCH 38/49] Appease linters --- playbooks/files/dynamic_inventory.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/playbooks/files/dynamic_inventory.py b/playbooks/files/dynamic_inventory.py index 4a5a45ef8..4a8b64925 100755 --- a/playbooks/files/dynamic_inventory.py +++ b/playbooks/files/dynamic_inventory.py @@ -87,7 +87,7 @@ # - Removed from test due to unrelated IP POOL errors. # - It appears that fabric would need to have SUBNET # resource added? - # + # elif nd_role == "vrf_lite": # VXLAN/EVPN Fabric Name # Uses fabric_1 @@ -97,9 +97,6 @@ switch_2 = spine_2 # switch_3: vrf-lite capable switch_3 = bgw_1 - interface_1 = interface_1 - interface_2 = interface_2 - interface_3 = interface_3 else: switch_1 = leaf_1 switch_2 = spine_1 @@ -142,7 +139,7 @@ "interface_1": interface_1, "interface_2": interface_2, "interface_3": interface_3, - "testcase": nd_testcase + "testcase": nd_testcase, }, }, "dcnm": { From 8fb0907b20f95b336005302646550ef5e06c5f86 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 10 Dec 2024 11:21:13 -1000 Subject: [PATCH 39/49] Fix unprotected dictionary access Found in IT (replaced.yaml) --- plugins/modules/dcnm_vrf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 1b377c64e..664672382 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -877,7 +877,8 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want["deployment"] = True attach_list.append(want) if want_is_deploy is True: - del want["isAttached"] + if "isAttached" in want: + del want["isAttached"] deploy_vrf = True continue From 509c335d5c2c21fb4fa0d679f60274ec2c1597e1 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 10 Dec 2024 11:33:20 -1000 Subject: [PATCH 40/49] Update integration tests 1. All tests - Added wait_for to the CLEANUP section for all tests, in case we run them immediately after each other. - rename "result" to include the test number i.e. there is no longer a global result that is overwritten in each test. Rather, each test has its own result. This is a bit more maintenance perhaps, but it reduces the probability that one test asserts on an earlier test's result. 2. replaced.yaml - Added REQUIRED VARS section - Use standardized var names - SETUP: print vars - Add standardized task names to all tasks - Renumbered tests to group them. - Print all results just prior to their associated asserts 3. query.yaml - Update some task titles and fix some test numbers 4. merged.yaml - Print all results just prior to their associated asserts - Fix a few test numbering issues 5. deleted.yaml - Use standardize task titles --- .../targets/dcnm_vrf/tests/dcnm/deleted.yaml | 248 ++++++------ .../targets/dcnm_vrf/tests/dcnm/merged.yaml | 76 ++-- .../dcnm_vrf/tests/dcnm/overridden.yaml | 4 + .../targets/dcnm_vrf/tests/dcnm/query.yaml | 148 ++++---- .../targets/dcnm_vrf/tests/dcnm/replaced.yaml | 359 +++++++++++------- 5 files changed, 474 insertions(+), 361 deletions(-) diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml index 8ed6c01dd..34001a9c1 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml @@ -38,28 +38,28 @@ - "switch_2 : {{ switch_2 }}" - "interface_2 : {{ interface_2 }}" -- name: SETUP.1 - DELETED - Verify fabric is deployed. +- name: SETUP.1 - DELETED - [dcnm_rest.GET] Verify fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" - register: result + register: result_setup_1 - assert: that: - - 'result.response.DATA != None' + - 'result_setup_1.response.DATA != None' -- name: SETUP.2 - DELETED - Delete all VRFs +- name: SETUP.2 - DELETED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted register: result_setup_2 -- name: SETUP.2a - DELETED - Wait 40 seconds for controller and switch to sync +- name: SETUP.2a - DELETED - [wait_for] Wait 40 seconds for controller and switch to sync wait_for: timeout: 40 when: result_setup_2.changed == true -- name: SETUP.3 - DELETED - Create, Attach and Deploy VLAN+VRF +- name: SETUP.3 - DELETED - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -73,38 +73,38 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_setup_3 -- name: SETUP.4 - DELETED - Wait for vrfStatus == DEPLOYED +- name: SETUP.4 - DELETED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_setup_4 until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_setup_4.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_setup_3.changed == true' + - 'result_setup_3.response[0].RETURN_CODE == 200' + - 'result_setup_3.response[1].RETURN_CODE == 200' + - 'result_setup_3.response[2].RETURN_CODE == 200' + - '(result_setup_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_setup_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_setup_3.diff[0].attach[0].deploy == true' + - 'result_setup_3.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_setup_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_setup_3.diff[0].attach[1].ip_address' + - 'result_setup_3.diff[0].vrf_name == "ansible-vrf-int1"' ############################################### ### DELETED ## ############################################### -- name: TEST.1 - DELETED - Delete VRF ansible-vrf-int1 - cisco.dcnm.dcnm_vrf: &conf +- name: TEST.1 - DELETED - [deleted] Delete VRF ansible-vrf-int1 + cisco.dcnm.dcnm_vrf: &conf1 fabric: "{{ fabric_1 }}" state: deleted config: @@ -112,37 +112,37 @@ vrf_id: 9008011 vrf_template: Default_VRF_Universal vrf_extension_template: Default_VRF_Extension_Universal - register: result + register: result_1 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[1].MESSAGE == "OK"' - - 'result.response[2].RETURN_CODE == 200' - - 'result.response[2].METHOD == "DELETE"' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == false' - - 'result.diff[0].attach[1].deploy == false' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.1a - DELETED - Wait 40 seconds for controller and switch to sync + - 'result_1.changed == true' + - 'result_1.response[0].RETURN_CODE == 200' + - 'result_1.response[1].RETURN_CODE == 200' + - 'result_1.response[1].MESSAGE == "OK"' + - 'result_1.response[2].RETURN_CODE == 200' + - 'result_1.response[2].METHOD == "DELETE"' + - '(result_1.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_1.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_1.diff[0].attach[0].deploy == false' + - 'result_1.diff[0].attach[1].deploy == false' + - 'result_1.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.1a - DELETED - [wait_for] Wait 40 seconds for controller and switch to sync wait_for: timeout: 40 -- name: TEST.2 - DELETED - conf - Idempotence - cisco.dcnm.dcnm_vrf: *conf - register: result +- name: TEST.1b - DELETED - [deleted] conf1 - Idempotence + cisco.dcnm.dcnm_vrf: *conf1 + register: result_1b - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' - - 'result.diff|length == 0' + - 'result_1b.changed == false' + - 'result_1b.response|length == 0' + - 'result_1b.diff|length == 0' -- name: TEST.3 - DELETED - Create, Attach and Deploy new VLAN+VRF+LITE EXTENSION 1x switch +- name: TEST.2 - DELETED - [merged] Create, Attach, Deploy VLAN+VRF+LITE EXTENSION ansible-vrf-int1 switch_2 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -164,34 +164,34 @@ neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true - register: result + register: result_2 -- name: TEST.4 - DELETED - Wait for vrfStatus == DEPLOYED +- name: TEST.2a - DELETED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_2a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_2a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.5 - DELETED - Delete VRF+LITE EXTENSION 1x switch - cisco.dcnm.dcnm_vrf: &conf1 + - 'result_2.changed == true' + - 'result_2.response[0].RETURN_CODE == 200' + - 'result_2.response[1].RETURN_CODE == 200' + - 'result_2.response[2].RETURN_CODE == 200' + - '(result_2.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2.diff[0].attach[0].deploy == true' + - 'result_2.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_2.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_2.diff[0].attach[1].ip_address' + - 'result_2.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.2b - DELETED - [deleted] Delete VRF+LITE EXTENSION ansible-vrf-int1 switch_2 + cisco.dcnm.dcnm_vrf: &conf2 fabric: "{{ fabric_1 }}" state: deleted config: @@ -212,40 +212,40 @@ neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # controller can provide dot1q deploy: true - register: result + register: result_2b - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[1].MESSAGE == "OK"' - - 'result.response[2].RETURN_CODE == 200' - - 'result.response[2].METHOD == "DELETE"' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == false' - - 'result.diff[0].attach[1].deploy == false' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.5a - DELETED - Wait 40 seconds for controller and switch to sync + - 'result_2b.changed == true' + - 'result_2b.response[0].RETURN_CODE == 200' + - 'result_2b.response[1].RETURN_CODE == 200' + - 'result_2b.response[1].MESSAGE == "OK"' + - 'result_2b.response[2].RETURN_CODE == 200' + - 'result_2b.response[2].METHOD == "DELETE"' + - '(result_2b.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2b.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2b.diff[0].attach[0].deploy == false' + - 'result_2b.diff[0].attach[1].deploy == false' + - 'result_2b.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.2c - DELETED - [wait_for] Wait 40 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: timeout: 40 -- name: TEST.6 - DELETED - conf1 - Idempotence - cisco.dcnm.dcnm_vrf: *conf1 - register: result +- name: TEST.2d - DELETED - [deleted] conf2 - Idempotence + cisco.dcnm.dcnm_vrf: *conf2 + register: result_2d - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' - - 'result.diff|length == 0' + - 'result_2d.changed == false' + - 'result_2d.response|length == 0' + - 'result_2d.diff|length == 0' -- name: TEST.7 - DELETED - Create, Attach, Deploy VRF+LITE EXTENSION switch_2 +- name: TEST.3 - DELETED - [merged] Create, Attach, Deploy VRF+LITE EXTENSION switch_2 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -267,75 +267,79 @@ neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true - register: result + register: result_3 -- name: TEST.8 - DELETED - Wait for vrfStatus == DEPLOYED +- name: TEST.3a - DELETED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_3a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_3a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.9 - DELETED - Delete VRF+LITE EXTENSION - empty config element - cisco.dcnm.dcnm_vrf: &conf2 + - 'result_3.changed == true' + - 'result_3.response[0].RETURN_CODE == 200' + - 'result_3.response[1].RETURN_CODE == 200' + - 'result_3.response[2].RETURN_CODE == 200' + - '(result_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3.diff[0].attach[0].deploy == true' + - 'result_3.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' + - 'result_3.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.3b - DELETED - [deleted] Delete VRF+LITE EXTENSION - empty config element + cisco.dcnm.dcnm_vrf: &conf3 fabric: "{{ fabric_1 }}" state: deleted config: - register: result + register: result_3b - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[1].MESSAGE == "OK"' - - 'result.response[2].RETURN_CODE == 200' - - 'result.response[2].METHOD == "DELETE"' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == false' - - 'result.diff[0].attach[1].deploy == false' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.10a - DELETED - Wait 40 seconds for controller and switch to sync + - 'result_3b.changed == true' + - 'result_3b.response[0].RETURN_CODE == 200' + - 'result_3b.response[1].RETURN_CODE == 200' + - 'result_3b.response[1].MESSAGE == "OK"' + - 'result_3b.response[2].RETURN_CODE == 200' + - 'result_3b.response[2].METHOD == "DELETE"' + - '(result_3b.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3b.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3b.diff[0].attach[0].deploy == false' + - 'result_3b.diff[0].attach[1].deploy == false' + - 'result_3b.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.3c - DELETED - [wait_for] Wait 40 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: timeout: 40 -- name: TEST.10 - DELETED - conf1 - Idempotence - cisco.dcnm.dcnm_vrf: *conf2 - register: result +- name: TEST.3d - DELETED - conf3 - Idempotence + cisco.dcnm.dcnm_vrf: *conf3 + register: result_3d - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' - - 'result.diff|length == 0' + - 'result_3d.changed == false' + - 'result_3d.response|length == 0' + - 'result_3d.diff|length == 0' ################################################ #### CLEAN-UP ## ################################################ -- name: CLEANUP.1 - DELETED - Delete all VRFs +- name: CLEANUP.1 - DELETED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted + +- name: CLEANUP.2 - DELETED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml index 6a42fd841..fbd5ab38f 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml @@ -36,7 +36,7 @@ rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" -- name: SETUP.0 - MERGED - print vars +- name: SETUP.0 - MERGED - [with_items] print vars ansible.builtin.debug: var: item with_items: @@ -91,20 +91,20 @@ deploy: true register: result_1 -- name: TEST.1a - MERGED - [debug] print result_1 - ansible.builtin.debug: - var: result_1 - -- name: TEST.1b - MERGED - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.1a - MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: result_1b + register: result_1a until: - - "result_1b.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_1a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.1b - MERGED - [debug] print result_1 + ansible.builtin.debug: + var: result_1 + - assert: that: - 'result_1.changed == true' @@ -123,6 +123,10 @@ cisco.dcnm.dcnm_vrf: *conf1 register: result_1c +- name: TEST.1d - MERGED - [debug] print result_1c + ansible.builtin.debug: + var: result_1c + - assert: that: - 'result_1c.changed == false' @@ -157,20 +161,20 @@ deploy: true register: result_2 -- name: TEST.2a - MERGED - [debug] print result_2 - ansible.builtin.debug: - var: result_2 - -- name: TEST.2b - MERGED - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.2a - MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: result_2b + register: result_2a until: - - "result_2b.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_2a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.2b - MERGED - [debug] print result_2 + ansible.builtin.debug: + var: result_2 + - assert: that: - 'result_2.changed == true' @@ -233,20 +237,20 @@ deploy: true register: result_3 -- name: TEST.3a - MERGED - [debug] print result_3 - ansible.builtin.debug: - var: result_3 - -- name: TEST.3b - MERGED - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.3a - MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: result_3b + register: result_3a until: - - "result_3b.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_3a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.3b - MERGED - [debug] print result_3 + ansible.builtin.debug: + var: result_3 + - assert: that: - 'result_3.changed == true' @@ -261,25 +265,25 @@ - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' - 'result_3.diff[0].vrf_name == "ansible-vrf-int1"' -- name: TEST.3b - MERGED - [merged] conf3 - Idempotence +- name: TEST.3c - MERGED - [merged] conf3 - Idempotence cisco.dcnm.dcnm_vrf: *conf3 - register: result_3b + register: result_3c -- name: TEST.3c - MERGED - [debug] print result_3b +- name: TEST.3d - MERGED - [debug] print result_3c ansible.builtin.debug: - var: result_3b + var: result_3c - assert: that: - - 'result_3b.changed == false' - - 'result_3b.response|length == 0' + - 'result_3c.changed == false' + - 'result_3c.response|length == 0' -- name: TEST.3d - MERGED - [deleted] Delete all VRFs +- name: TEST.3e - MERGED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted -- name: TEST.3e - MERGED - Wait 60 seconds for controller and switch to sync +- name: TEST.3f - MERGED - Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state @@ -304,10 +308,6 @@ deploy: true register: result_4 -- name: TEST.4a - MERGED - [debug] print result_4 - ansible.builtin.debug: - var: result_4 - - name: TEST.4a - MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" @@ -318,6 +318,10 @@ retries: 30 delay: 2 +- name: TEST.4b - MERGED - [debug] print result_4 + ansible.builtin.debug: + var: result_4 + - assert: that: - 'result_4.changed == true' @@ -451,3 +455,7 @@ cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted + +- name: CLEANUP.2 - MERGED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml index d1212b002..0ba1c5465 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml @@ -395,3 +395,7 @@ cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted + +- name: CLEANUP.2 - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml index d654f2c5e..ec0b4c8a0 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml @@ -27,7 +27,7 @@ rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" -- name: SETUP.0 - QUERY - print vars +- name: SETUP.0 - QUERY - [with_items] print vars ansible.builtin.debug: var: item with_items: @@ -60,7 +60,7 @@ timeout: 40 when: result_setup_2.changed == true -- name: SETUP.4 - QUERY - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 +- name: SETUP.3 - QUERY - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -74,31 +74,35 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_setup_3 -- name: SETUP.5 - [query] Wait for vrfStatus == DEPLOYED +- name: SETUP.3a - QUERY - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_setup_3a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_setup_3a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: SETUP.3b - QUERY - [debug] print result_setup_3 + ansible.builtin.debug: + var: result_setup_3 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_setup_3.changed == true' + - 'result_setup_3.response[0].RETURN_CODE == 200' + - 'result_setup_3.response[1].RETURN_CODE == 200' + - 'result_setup_3.response[2].RETURN_CODE == 200' + - '(result_setup_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_setup_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_setup_3.diff[0].attach[0].deploy == true' + - 'result_setup_3.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_setup_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_setup_3.diff[0].attach[1].ip_address' + - 'result_setup_3.diff[0].vrf_name == "ansible-vrf-int1"' # ############################################### # ### QUERY ## @@ -120,7 +124,7 @@ deploy: true register: result_1 -- name: TEST.1b print result_1 +- name: TEST.1b - QUERY - [debug] print result_1 debug: var: result_1 @@ -143,7 +147,7 @@ state: deleted register: result_2 -- name: TEST.2a print result_2 +- name: TEST.2a - QUERY - [debug] print result_2 debug: var: result_2 @@ -181,20 +185,20 @@ deploy: true register: result_3 -- name: TEST.3a print result_3 - debug: - var: result_3 - -- name: TEST.4 - QUERY - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.3a - QUERY - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_3a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_3a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.3b - QUERY - [debug] print result_3 + debug: + var: result_3 + - assert: that: - 'result_3.changed == true' @@ -207,7 +211,7 @@ - '"{{ switch_1 }}" in result_3.diff[0].attach[0].ip_address' - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' -- name: TEST.5 - QUERY - [merged] Create, Attach, Deploy VRF+LITE EXTENSION ansible-vrf-int2 on switch_2 +- name: TEST.4 - QUERY - [merged] Create, Attach, Deploy VRF+LITE EXTENSION ansible-vrf-int2 on switch_2 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -231,20 +235,20 @@ deploy: true register: result_4 -- name: TEST.5a print result_4 - debug: - var: result_4 - -- name: TEST.6 - QUERY - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.4a - QUERY - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_4a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_4a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.4b - QUERY - [debug] print result_4 + debug: + var: result_4 + - assert: that: - 'result_4.changed == true' @@ -255,7 +259,7 @@ - '"{{ switch_2 }}" in result_4.diff[0].attach[0].ip_address' - 'result_4.diff[0].vrf_name == "ansible-vrf-int2"' -- name: TEST.7 - QUERY - [query] Query VRF+LITE EXTENSION ansible-vrf-int2 switch_2 +- name: TEST.5 - QUERY - [query] Query VRF+LITE EXTENSION ansible-vrf-int2 switch_2 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query @@ -277,41 +281,49 @@ neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true - register: result + register: result_5 + +- name: TEST.5a - QUERY - [debug] print result_5 + debug: + var: result_5 - assert: that: - - 'result.changed == false' - - 'result.response[0].parent.vrfName == "ansible-vrf-int2"' - - 'result.response[0].parent.vrfId == 9008012' - - 'result.response[0].parent.vrfStatus == "DEPLOYED"' - - 'result.response[0].attach[0].switchDetailsList[0].islanAttached == true' - - 'result.response[0].attach[0].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - - 'result.response[0].attach[0].switchDetailsList[0].vlan == 1500' - - 'result.response[0].attach[1].switchDetailsList[0].islanAttached == true' - - 'result.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - - 'result.response[0].attach[1].switchDetailsList[0].vlan == 1500' - -- name: TEST.8 - QUERY - [query] Query without the config element + - 'result_5.changed == false' + - 'result_5.response[0].parent.vrfName == "ansible-vrf-int2"' + - 'result_5.response[0].parent.vrfId == 9008012' + - 'result_5.response[0].parent.vrfStatus == "DEPLOYED"' + - 'result_5.response[0].attach[0].switchDetailsList[0].islanAttached == true' + - 'result_5.response[0].attach[0].switchDetailsList[0].lanAttachedState == "DEPLOYED"' + - 'result_5.response[0].attach[0].switchDetailsList[0].vlan == 1500' + - 'result_5.response[0].attach[1].switchDetailsList[0].islanAttached == true' + - 'result_5.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' + - 'result_5.response[0].attach[1].switchDetailsList[0].vlan == 1500' + +- name: TEST.6 - QUERY - [query] Query without the config element cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: result + register: result_6 + +- name: TEST.6a - QUERY - [debug] print result_6 + debug: + var: result_6 - assert: that: - - 'result.changed == false' - - 'result.response[0].parent.vrfName == "ansible-vrf-int2"' - - 'result.response[0].parent.vrfId == 9008012' - - 'result.response[0].parent.vrfStatus == "DEPLOYED"' - - 'result.response[0].attach[0].switchDetailsList[0].islanAttached == true' - - 'result.response[0].attach[0].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - - 'result.response[0].attach[0].switchDetailsList[0].vlan == 1500' - - 'result.response[0].attach[1].switchDetailsList[0].islanAttached == true' - - 'result.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - - 'result.response[0].attach[1].switchDetailsList[0].vlan == 1500' - -- name: TEST.9 - QUERY - [query] Query non-existent VRF ansible-vrf-int1 + - 'result_6.changed == false' + - 'result_6.response[0].parent.vrfName == "ansible-vrf-int2"' + - 'result_6.response[0].parent.vrfId == 9008012' + - 'result_6.response[0].parent.vrfStatus == "DEPLOYED"' + - 'result_6.response[0].attach[0].switchDetailsList[0].islanAttached == true' + - 'result_6.response[0].attach[0].switchDetailsList[0].lanAttachedState == "DEPLOYED"' + - 'result_6.response[0].attach[0].switchDetailsList[0].vlan == 1500' + - 'result_6.response[0].attach[1].switchDetailsList[0].islanAttached == true' + - 'result_6.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' + - 'result_6.response[0].attach[1].switchDetailsList[0].vlan == 1500' + +- name: TEST.7 - QUERY - [query] Query non-existent VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query @@ -325,12 +337,16 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_7 + +- name: TEST.7a - QUERY - [debug] print result_7 + debug: + var: result_7 - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_7.changed == false' + - 'result_7.response|length == 0' ############################################### ### CLEAN-UP ## @@ -340,3 +356,7 @@ cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted + +- name: CLEANUP.2 - QUERY - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml index 0a555609c..2ac00ad55 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml @@ -1,16 +1,44 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# A VXLAN_EVPN fabric +# +# switch_1 +# A vrf-lite capable switch +# Does not require an interface +# +# switch_2 +# A vrf-lite capable switch +# +# interface_2 +# Ethernet interface on switch_2 +# used to test VRF LITE configuration. +# +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" -- name: REPLACED - Verify if fabric is deployed. +- name: SETUP.0 - REPLACED - [with_items] print vars + ansible.builtin.debug: + var: item + with_items: + - "fabric_1 : {{ fabric_1 }}" + - "switch_1 : {{ switch_1 }}" + - "switch_2 : {{ switch_2 }}" + - "interface_2 : {{ interface_2 }}" + +- name: SETUP.1 - REPLACED - [dcnm_rest.GET] Verify if fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" @@ -20,14 +48,18 @@ that: - 'result.response.DATA != None' -- name: REPLACED - Clean up any existing vrfs +- name: SETUP.2 - REPLACED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted -- name: REPLACED - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: SETUP.3 - REPLACED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 + +- name: SETUP.4 - REPLACED - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -36,40 +68,44 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_setup_4 -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: SETUP.4a - REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_setup_4a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_setup_4a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: SETUP.4b - REPLACED - [debug] print result_setup_4 + ansible.builtin.debug: + var: result_setup_4 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_setup_4.changed == true' + - 'result_setup_4.response[0].RETURN_CODE == 200' + - 'result_setup_4.response[1].RETURN_CODE == 200' + - 'result_setup_4.response[2].RETURN_CODE == 200' + - '(result_setup_4.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_setup_4.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_setup_4.diff[0].attach[0].deploy == true' + - 'result_setup_4.diff[0].attach[1].deploy == true' + - 'result_setup_4.diff[0].vrf_name == "ansible-vrf-int1"' ############################################### ### REPLACED ## ############################################### -- name: REPLACED - Update existing VRF using replace - delete attachments +- name: TEST.1 - REPLACED - [replaced] Update existing VRF using replace - delete attachments cisco.dcnm.dcnm_vrf: &conf1 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: replaced config: - vrf_name: ansible-vrf-int1 @@ -77,40 +113,48 @@ vrf_template: Default_VRF_Universal vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 - register: result + register: result_1 -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.1a - REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_1a until: - - "query_result.response[0].parent.vrfStatus is search('NA')" + - "result_1a.response[0].parent.vrfStatus is search('NA')" retries: 30 delay: 2 +- name: TEST.1b - REPLACED - [debug] print result_1 + debug: + var: result_1 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == false' - - 'result.diff[0].attach[1].deploy == false' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: REPLACED - conf1 - Idempotence + - 'result_1.changed == true' + - 'result_1.response[0].RETURN_CODE == 200' + - 'result_1.response[1].RETURN_CODE == 200' + - '(result_1.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_1.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_1.diff[0].attach[0].deploy == false' + - 'result_1.diff[0].attach[1].deploy == false' + - 'result_1.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.1c - REPLACED - conf1 - Idempotence cisco.dcnm.dcnm_vrf: *conf1 - register: result + register: result_1c + +- name: TEST.1d - REPLACED - [debug] print result_1c + debug: + var: result_1c - assert: that: - - 'result.changed == false' + - 'result_1c.changed == false' -- name: REPLACED - Update existing VRF using replace - create attachments +- name: TEST.2 - REPLACED - [replaced] Update existing VRF using replace - create attachments cisco.dcnm.dcnm_vrf: &conf2 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: replaced config: - vrf_name: ansible-vrf-int1 @@ -119,50 +163,62 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_2 -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.2a - REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_2a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_2a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.2b - REPLACED - [debug] print result_2 + debug: + var: result_2 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - - 'result.diff[0].attach[0].vlan_id == 500' - - 'result.diff[0].attach[1].vlan_id == 500' - -- name: REPLACED - conf2 - Idempotence + - 'result_2.changed == true' + - 'result_2.response[0].RETURN_CODE == 200' + - 'result_2.response[1].RETURN_CODE == 200' + - '(result_2.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2.diff[0].attach[0].deploy == true' + - 'result_2.diff[0].attach[1].deploy == true' + - 'result_2.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_2.diff[0].attach[0].vlan_id == 500' + - 'result_2.diff[0].attach[1].vlan_id == 500' + +- name: TEST.2c - REPLACED - [replaced] conf2 - Idempotence cisco.dcnm.dcnm_vrf: *conf2 - register: result + register: result_2c + +- name: TEST.2d - REPLACED - [debug] print result_2c + debug: + var: result_2c - assert: that: - - 'result.changed == false' + - 'result_2c.changed == false' -- name: REPLACED - Clean up any existing vrfs +- name: TEST.2e - REPLACED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted -- name: REPLACED - Create, Attach and Deploy new VRF - VLAN/VRF LITE EXTENSION Provided by the User in one switch +- name: TEST.2f - REPLACED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 + +- name: TEST.3 - REPLACED - [merged] Create, Attach, Deploy VLAN+VRF LITE EXTENSION switch_2 (user provided) cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -171,46 +227,50 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_2 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true - register: result + register: result_3 -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.3a - REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_3a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_3a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.3b - REPLACED - [debug] print result_3 + debug: + var: result_3 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: REPLACED - Update existing VRF LITE extensions using Replace - Delete VRF LITE Attachment Only - cisco.dcnm.dcnm_vrf: &conf3 - fabric: "{{ test_fabric }}" + - 'result_3.changed == true' + - 'result_3.response[0].RETURN_CODE == 200' + - 'result_3.response[1].RETURN_CODE == 200' + - 'result_3.response[2].RETURN_CODE == 200' + - '(result_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3.diff[0].attach[0].deploy == true' + - 'result_3.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' + - 'result_3.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.4 - REPLACED - [replaced] Update existing VRF LITE extensions using Replace - Delete VRF LITE Attachment Only + cisco.dcnm.dcnm_vrf: &conf4 + fabric: "{{ fabric_1 }}" state: replaced config: - vrf_name: ansible-vrf-int1 @@ -219,47 +279,52 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" + - ip_address: "{{ switch_1 }}" deploy: true - register: result + register: result_4 + +- name: TEST.4a - REPLACED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.4b - REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_4b until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_4b.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.4c - REPLACED - [debug] print result_4 + debug: + var: result_4 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == false' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: REPLACED - conf3 - Idempotence - cisco.dcnm.dcnm_vrf: *conf3 - register: result + - 'result_4.changed == true' + - 'result_4.response[0].RETURN_CODE == 200' + - 'result_4.response[1].RETURN_CODE == 200' + - '(result_4.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - 'result_4.diff[0].attach[0].deploy == false' + - 'result_4.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.4d - REPLACED - conf4 - Idempotence + cisco.dcnm.dcnm_vrf: *conf4 + register: result_4d + +- name: TEST.4d - REPLACED - [debug] print result_4d + debug: + var: result_4d - assert: that: - - 'result.changed == false' + - 'result_4d.changed == false' -- name: QUERY - sleep for 40 seconds for DCNM to completely update the state - # The vrf lite profile removal returns ok for deployment, but the switch takes time to remove - # the profile so wait for some time before creating a new vrf, else the switch goes into - # OUT-OF-SYNC state - wait_for: - timeout: 40 - -- name: REPLACED - Update existing VRF LITE extensions using Replace - Create VRF LITE Attachment Only - cisco.dcnm.dcnm_vrf: &conf4 - fabric: "{{ test_fabric }}" +- name: TEST.5 - REPLACED - [replaced] Update existing VRF LITE extensions using Replace - Create VRF LITE Attachment Only + cisco.dcnm.dcnm_vrf: &conf5 + fabric: "{{ fabric_1 }}" state: replaced config: - vrf_name: ansible-vrf-int1 @@ -268,52 +333,64 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_2 }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm deploy: true - register: result + register: result_5 -- name: Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.5a - REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_5a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_5a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.5b - REPLACED - [debug] print result_5 + debug: + var: result_5 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - - 'result.diff[0].attach[0].vlan_id == 500' - -- name: REPLACED - conf4 - Idempotence - cisco.dcnm.dcnm_vrf: *conf4 - register: result + - 'result_5.changed == true' + - 'result_5.response[0].RETURN_CODE == 200' + - 'result_5.response[1].RETURN_CODE == 200' + - '(result_5.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - 'result_5.diff[0].attach[0].deploy == true' + - 'result_5.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_5.diff[0].attach[0].vlan_id == 500' + +- name: TEST.5c - REPLACED - conf5 - Idempotence + cisco.dcnm.dcnm_vrf: *conf5 + register: result_5c + +- name: TEST.5d - REPLACED - [debug] print result_5c + debug: + var: result_5c - assert: that: - - 'result.changed == false' + - 'result_5c.changed == false' ############################################### ### CLEAN-UP ## ############################################### -- name: REPLACED - Clean up any existing vrfs +- name: CLEANUP.1 - REPLACED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted + +- name: CLEANUP.2 - REPLACED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 From b61fd0bde3e6a39bd9cf42cf2907cfb3332ce45a Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 10 Dec 2024 14:01:32 -1000 Subject: [PATCH 41/49] dcnm_vrf/dcnm_tests.yaml - Include all vars 1. Include all vars used in the dcnm_vrf integration tests. 2. Update the path to dynamic_inventory.py --- playbooks/roles/dcnm_vrf/dcnm_tests.yaml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/playbooks/roles/dcnm_vrf/dcnm_tests.yaml b/playbooks/roles/dcnm_vrf/dcnm_tests.yaml index 3b6b3d24a..e4e9c90aa 100644 --- a/playbooks/roles/dcnm_vrf/dcnm_tests.yaml +++ b/playbooks/roles/dcnm_vrf/dcnm_tests.yaml @@ -5,31 +5,43 @@ # REPO_ROOT/tests/integration/targets/dcnm_vrf/tests/dcnm/*.yaml # # Either: +# # 1. Modify the following: +# # - The vars section below with details for your testing setup. # - dcnm_hosts.yaml in this directory # # 2. Run the tests +# # ansible-playbook dcnm_tests.yaml -i dcnm_hosts.yaml # # OR: -# 1. Modify ../dynamic_inventory.py to align with your setup +# +# 1. Modify ../../files/dynamic_inventory.py to align with your setup +# # This must contain the vars mentioned below and controller # info from dcnm_hosts.yaml (modified for your setup) +# # 2. Run the tests -# ansible-playbook dcnm_tests.yaml -i ../files/dynamic_inventory.py # -# NOTES: +# ansible-playbook dcnm_tests.yaml -i ../../files/dynamic_inventory.py +# +# - hosts: dcnm gather_facts: no connection: ansible.netcommon.httpapi # Uncomment and modify if not using dynamic_inventory.py +# See the individual test yaml files for a description of +# how each var below is used in each test. Some tests, +# for example, do not use interface_1. # vars: - # test_fabric: f1 + # fabric_1: f1 # switch_1: 10.1.1.2 # switch_2: 10.1.1.3 # switch_3: 10.1.1.4 - # interface_1: Ethernet1/2 + # interface_1: Ethernet1/1 + # interface_2: Ethernet1/2 + # interface_3: Ethernet1/3 ## Uncomment ONE of the following testcases # testcase: deleted # testcase: merged From a565a46f635805a8a3b8f707073011a9aeb1c6fd Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 10 Dec 2024 14:25:03 -1000 Subject: [PATCH 42/49] Update Usage section and assign additional fabric_* vars Address mwiebe comments by including more detailed usage and examples. Add fabric_2, fabric_3 in case any tests require more than one fabric. --- playbooks/files/dynamic_inventory.py | 60 +++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/playbooks/files/dynamic_inventory.py b/playbooks/files/dynamic_inventory.py index 4a8b64925..6c4365726 100755 --- a/playbooks/files/dynamic_inventory.py +++ b/playbooks/files/dynamic_inventory.py @@ -31,13 +31,59 @@ # Usage -See README.md in the top-level of this repository and define the environment -variables described there appropriately for your environment. +## Mandatory general variables + +The following general environment variables are related +to credentials, controller reachability, and role/testcase +assignment. These should be considered mandatory; though +the NXOS_* variables are not strictly needed unless called +for by the specific role/testcase. + +Values below are examples, and should be modified for your +setup and the roles/testcases you are running. + +```bash +export NDFC_ROLE=dcnm_vrf # The role to run +export ND_TESTCASE=query # The testcase to run +export ND_IP4=10.1.1.1 # Controller IPv4 address +export ND_PASSWORD=MyPassword # Controller password +export ND_USERNAME=admin # Controller username +export NXOS_PASSWORD=MyPassword # Switch password +export NXOS_USERNAME=admin # Switch username +``` + +## Fabrics + +We can add more fabrics later as the need arises... + +```bash +export ND_FABRIC_1=MyFabric1 # Assigned to var fabric_1 +export ND_FABRIC_2=MyFabric2 # Assigned to var fabric_2 +export ND_FABRIC_3=MyFabric3 # Assigned to var fabric_3 + +``` + +## Interfaces + +Interface usage varies by testcase. See individual +testcase YAML files for details regarding each test's +usage. + +```bash +export ND_INTERFACE_1=Ethernet1/1 +export ND_INTERFACE_2=Ethernet1/2 +export ND_INTERFACE_3=Ethernet1/3 +``` + + """ nd_role = environ.get("ND_ROLE", "dcnm_vrf") nd_testcase = environ.get("ND_TESTCASE", "query") fabric_1 = environ.get("ND_FABRIC_1") +fabric_2 = environ.get("ND_FABRIC_1") +fabric_3 = environ.get("ND_FABRIC_1") + nd_ip4 = environ.get("ND_IP4") nd_password = environ.get("ND_PASSWORD") nd_username = environ.get("ND_USERNAME", "admin") @@ -54,6 +100,14 @@ spine_1 = environ.get("ND_SPINE_1_IP4", "10.1.1.112") spine_2 = environ.get("ND_SPINE_2_IP4", "10.1.1.113") +# Placeholders if you'd rather directly set each of +# the switch vars instead of setting the switch vars +# from the switch roles above (as is done for dcnm_vrf +# below). +switch_1 = environ.get("ND_SWITCH_1_IP4", "10.1.1.112") +switch_2 = environ.get("ND_SWITCH_2_IP4", "10.1.1.113") +switch_3 = environ.get("ND_SWITCH_3_IP4", "10.1.1.108") + # Base set of interfaces interface_1 = environ.get("ND_INTERFACE_1", "Ethernet1/1") interface_2 = environ.get("ND_INTERFACE_2", "Ethernet1/2") @@ -117,6 +171,8 @@ "ansible_python_interpreter": "python", "ansible_user": nd_username, "fabric_1": fabric_1, + "fabric_2": fabric_2, + "fabric_3": fabric_3, "bgw1": bgw_1, "bgw2": bgw_2, "leaf1": leaf_1, From 001931e515613ec201aa9ca24fcea7827507b303 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 11 Dec 2024 11:03:24 -1000 Subject: [PATCH 43/49] IT: interface var naming change 1. The current interface var names did not incorporate a way to encode switch ownership. Modified the var naming to allow for specifying multiple interfaces per switch in such a way that the switch ownership of an interface is evident. This is documented in: playbooks/files/dynamic_inventory.py 2. Modified all dcnm_vrf test cases to align with this convention. - Updated test case header comments with the new usage - Updated all test case interface vars - Ran the following tests - deleted.yaml - overridden.yaml - replaced.yaml - query.yaml - sanity.yaml 3. dynamic_interface.py In addition to the changes above: - Fixed the documentation for environment variable ND_ROLE (previously it was misnamed NDFC_ROLE in the documentation, but was correct -- ND_ROLE -- in the actual usage). - Fix Markdown heading levels --- playbooks/files/dynamic_inventory.py | 86 ++- .../targets/dcnm_vrf/tests/dcnm/deleted.yaml | 26 +- .../targets/dcnm_vrf/tests/dcnm/merged.yaml | 44 +- .../dcnm_vrf/tests/dcnm/overridden.yaml | 24 +- .../targets/dcnm_vrf/tests/dcnm/query.yaml | 22 +- .../targets/dcnm_vrf/tests/dcnm/replaced.yaml | 24 +- .../targets/dcnm_vrf/tests/dcnm/sanity.yaml | 629 ++++++++++-------- .../self-contained-tests/deleted_vrf_all.yaml | 45 +- .../self-contained-tests/merged_vrf_all.yaml | 39 +- .../overridden_vrf_all.yaml | 55 +- .../replaced_vrf_all.yaml | 47 +- .../dcnm/self-contained-tests/scale.yaml | 21 +- .../dcnm/self-contained-tests/vrf_lite.yaml | 149 +++-- 13 files changed, 747 insertions(+), 464 deletions(-) diff --git a/playbooks/files/dynamic_inventory.py b/playbooks/files/dynamic_inventory.py index 6c4365726..a5abf16f7 100755 --- a/playbooks/files/dynamic_inventory.py +++ b/playbooks/files/dynamic_inventory.py @@ -26,12 +26,13 @@ """ # Summary -Dynamic inventory for DCNM Collection integration tests. -Inventory is built from environment variables. +Optional dynamic inventory for the ansible-dcnm repository +integration tests. This inventory is built from environment +variables. -# Usage +## Usage -## Mandatory general variables +### Mandatory general variables The following general environment variables are related to credentials, controller reachability, and role/testcase @@ -43,7 +44,7 @@ setup and the roles/testcases you are running. ```bash -export NDFC_ROLE=dcnm_vrf # The role to run +export ND_ROLE=dcnm_vrf # The role to run export ND_TESTCASE=query # The testcase to run export ND_IP4=10.1.1.1 # Controller IPv4 address export ND_PASSWORD=MyPassword # Controller password @@ -52,7 +53,7 @@ export NXOS_USERNAME=admin # Switch username ``` -## Fabrics +### Fabrics We can add more fabrics later as the need arises... @@ -63,18 +64,48 @@ ``` -## Interfaces +### Interfaces Interface usage varies by testcase. See individual testcase YAML files for details regarding each test's usage. +#### Interface naming convention + +##### Environment variables + +ND_INTERFACE_[A][b] + +Where: + +A - The number of the switch to which the interface belongs +b - An incrementing lower-case letter in range a-z + +###### Examples: + ```bash -export ND_INTERFACE_1=Ethernet1/1 -export ND_INTERFACE_2=Ethernet1/2 -export ND_INTERFACE_3=Ethernet1/3 +export ND_INTERFACE_1a=Ethernet1/1 +export ND_INTERFACE_2a=Ethernet1/1 +export ND_INTERFACE_2b=Ethernet1/2 +export ND_INTERFACE_3a=Ethernet2/4 ``` +Above: + +- switch_1 has one interface; Ethernet1/1 +- switch_2 two interfaces; Ethernet1/1 and Ethernet1/2 +- switch_3 has one interface; Ethernet2/4 + +##### Test case variables + +Interface variables within test cases follow the same convention +as above, but are lowercase, and remove the leading ND_. + +###### Examples + +interface_1a - 1st interface on switch_1 +interface_1b - 2st interface on switch_1 +etc... """ nd_role = environ.get("ND_ROLE", "dcnm_vrf") @@ -109,9 +140,10 @@ switch_3 = environ.get("ND_SWITCH_3_IP4", "10.1.1.108") # Base set of interfaces -interface_1 = environ.get("ND_INTERFACE_1", "Ethernet1/1") -interface_2 = environ.get("ND_INTERFACE_2", "Ethernet1/2") -interface_3 = environ.get("ND_INTERFACE_3", "Ethernet1/3") +interface_1a = environ.get("ND_INTERFACE_1a", "Ethernet1/1") +interface_2a = environ.get("ND_INTERFACE_2a", "Ethernet1/1") +interface_2b = environ.get("ND_INTERFACE_2b", "Ethernet1/2") +interface_3a = environ.get("ND_INTERFACE_3a", "Ethernet1/3") if nd_role == "dcnm_vrf": # VXLAN/EVPN Fabric Name @@ -129,18 +161,17 @@ # - merged # - NOT vrf-lite capable switch_3 = leaf_3 - # interface_1 + # interface_1a # - no tests - # interface_2 - # - all tests + # interface_2a + # - [deleted, merged, overridden, query, replaced, vrf_lite] + # - switch_2 VRF LITE extensions + # interface_2b + # - [vrf_lite] # - switch_2 VRF LITE extensions - # interface_3 - # - merged + # interface_3a + # - [merged, vrf_lite] # - switch_3 non-vrf-lite capable switch - # - overridden - # - Removed from test due to unrelated IP POOL errors. - # - It appears that fabric would need to have SUBNET - # resource added? # elif nd_role == "vrf_lite": # VXLAN/EVPN Fabric Name @@ -149,7 +180,7 @@ switch_1 = spine_1 # switch_2: vrf-lite capable switch_2 = spine_2 - # switch_3: vrf-lite capable + # switch_3: vrf capable switch_3 = bgw_1 else: switch_1 = leaf_1 @@ -170,6 +201,7 @@ "ansible_password": nd_password, "ansible_python_interpreter": "python", "ansible_user": nd_username, + "testcase": nd_testcase, "fabric_1": fabric_1, "fabric_2": fabric_2, "fabric_3": fabric_3, @@ -192,10 +224,10 @@ "switch_1": switch_1, "switch_2": switch_2, "switch_3": switch_3, - "interface_1": interface_1, - "interface_2": interface_2, - "interface_3": interface_3, - "testcase": nd_testcase, + "interface_1a": interface_1a, + "interface_2a": interface_2a, + "interface_2b": interface_2b, + "interface_3a": interface_3a, }, }, "dcnm": { diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml index 34001a9c1..94e19b807 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml @@ -2,18 +2,22 @@ ## REQUIRED VARS ## ############################################## # fabric_1 -# A VXLAN_EVPN fabric +# +# - A VXLAN_EVPN fabric # # switch_1 -# A vrf-lite capable switch -# Does not require an interface +# +# - A vrf-lite capable switch +# - Does not require an interface # # switch_2 -# A vrf-lite capable switch # -# interface_2 -# Ethernet interface on switch_2 -# used to test VRF LITE configuration. +# - A vrf-lite capable switch +# +# interface_2a +# +# - Ethernet interface on switch_2 +# - Used to test VRF LITE configuration. # ############################################## @@ -36,7 +40,7 @@ - "fabric_1 : {{ fabric_1 }}" - "switch_1 : {{ switch_1 }}" - "switch_2 : {{ switch_2 }}" - - "interface_2 : {{ interface_2 }}" + - "interface_2a : {{ interface_2a }}" - name: SETUP.1 - DELETED - [dcnm_rest.GET] Verify fabric is deployed. cisco.dcnm.dcnm_rest: @@ -157,7 +161,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -205,7 +209,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -260,7 +264,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml index fbd5ab38f..374c8ee39 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml @@ -5,23 +5,29 @@ # A VXLAN_EVPN fabric # # switch_1 -# A vrf-lite capable switch -# Does not require an interface. +# +# - A vrf-lite capable switch +# - Does not require an interface. # # switch_2 -# A vrf-lite capable switch +# +# - A vrf-lite capable switch # # switch_3 +# # A NON-vrf-lite capable switch # -# interface_2 -# Ethernet interface on switch_2 -# Used to test VRF LITE configuration. +# interface_2a +# +# - Ethernet interface on switch_2 +# - Used to test VRF LITE configuration. +# +# interface_3a # -# interface_3 -# Ethernet interface on switch_3 -# Used to verify error when applied VRF LITE -# extension on a non-vrf-lite capable switch. +# - Ethernet interface on switch_3 +# - Used to verify error when applying a +# VRF LITE extension on a non-vrf-lite +# capable switch. ############################################## ############################################## @@ -44,8 +50,8 @@ - "switch_1 : {{ switch_1 }}" - "switch_2 : {{ switch_2 }}" - "switch_3 : {{ switch_3 }}" - - "interface_2 : {{ interface_2 }}" - - "interface_3 : {{ interface_3 }}" + - "interface_2a : {{ interface_2a }}" + - "interface_3a : {{ interface_3a }}" - name: SETUP.1 - MERGED - [dcnm_rest.GET] Verify fabric is deployed. cisco.dcnm.dcnm_rest: @@ -132,19 +138,19 @@ - 'result_1c.changed == false' - 'result_1c.response|length == 0' -- name: TEST.1d - MERGED - [deleted] Delete all VRFs +- name: TEST.1e - MERGED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted - register: result_1d + register: result_1e -- name: TEST.1e - MERGED - [wait_for] Wait 60 seconds for controller and switch to sync +- name: TEST.1f - MERGED - [wait_for] Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: timeout: 60 - when: result_1d.changed == true + when: result_1e.changed == true - name: TEST.2 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF (controller provided) cisco.dcnm.dcnm_vrf: &conf2 @@ -228,7 +234,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -304,7 +310,7 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" vrf_lite: - - interface: "{{ interface_2 }}" # mandatory + - interface: "{{ interface_2a }}" # mandatory deploy: true register: result_4 @@ -428,7 +434,7 @@ - ip_address: "{{ switch_3 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_3 }}" # mandatory + interface: "{{ interface_3a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml index 0ba1c5465..bddf59af9 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml @@ -2,17 +2,21 @@ ## REQUIRED VARS ## ############################################## # fabric_1 -# A VXLAN_EVPN fabric +# +# - A VXLAN_EVPN fabric # # switch_1 -# A vrf-lite capable switch +# +# - A vrf-lite capable switch # # switch_2 -# A vrf-lite capable switch # -# interface_2 -# Ethernet interface on switch_2 -# Used to test VRF LITE configuration. +# - A vrf-lite capable switch +# +# interface_2a +# +# - Ethernet interface on switch_2 +# - Used to test VRF LITE configuration. # ############################################## @@ -47,7 +51,7 @@ - "fabric_1 : {{ fabric_1 }}" - "switch_1 : {{ switch_1 }}" - "switch_2 : {{ switch_2 }}" - - "interface_2 : {{ interface_2 }}" + - "interface_2a : {{ interface_2a }}" - name: SETUP.1 - OVERRIDDEN - [dcnn_rest.GET] Verify if fabric is deployed. cisco.dcnm.dcnm_rest: @@ -198,7 +202,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.1/24 # optional neighbor_ipv4: 10.33.0.0 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -254,7 +258,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.1/24 # optional neighbor_ipv4: 10.33.0.0 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -330,7 +334,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.1/24 # optional neighbor_ipv4: 10.33.0.0 # optional ipv6_addr: 2010::10:34:0:1/64 # optional diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml index ec0b4c8a0..f92d99bb7 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml @@ -2,17 +2,21 @@ ## REQUIRED VARS ## ############################################## # fabric_1 -# A VXLAN_EVPN fabric +# +# - A VXLAN_EVPN fabric # # switch_1 -# A vrf-lite capable switch +# +# - A vrf-lite capable switch # # switch_2 -# A vrf-lite capable switch # -# interface_2 -# Ethernet interface on switch_2 -# Used to test VRF LITE configuration. +# - A vrf-lite capable switch +# +# interface_2a +# +# - Ethernet interface on switch_2 +# - Used to test VRF LITE configuration. ############################################## ############################################## @@ -34,7 +38,7 @@ - "fabric_1 : {{ fabric_1 }}" - "switch_1 : {{ switch_1 }}" - "switch_2 : {{ switch_2 }}" - - "interface_2 : {{ interface_2 }}" + - "interface_2a : {{ interface_2a }}" - name: SETUP.1 - QUERY - [dcnm_rest.GET] Verify if fabric is deployed. cisco.dcnm.dcnm_rest: @@ -226,7 +230,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # peer_vrf is mandatory - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -274,7 +278,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int2 # peer_vrf is mandatory - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml index 2ac00ad55..caacdeb74 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml @@ -2,18 +2,22 @@ ## REQUIRED VARS ## ############################################## # fabric_1 -# A VXLAN_EVPN fabric +# +# - A VXLAN_EVPN fabric # # switch_1 -# A vrf-lite capable switch -# Does not require an interface +# +# - A vrf-lite capable switch +# - Does not require an interface # # switch_2 -# A vrf-lite capable switch # -# interface_2 -# Ethernet interface on switch_2 -# used to test VRF LITE configuration. +# - A vrf-lite capable switch +# +# interface_2a +# +# - Ethernet interface on switch_2 +# - Used to test VRF LITE configuration. # ############################################## @@ -36,7 +40,7 @@ - "fabric_1 : {{ fabric_1 }}" - "switch_1 : {{ switch_1 }}" - "switch_2 : {{ switch_2 }}" - - "interface_2 : {{ interface_2 }}" + - "interface_2a : {{ interface_2a }}" - name: SETUP.1 - REPLACED - [dcnm_rest.GET] Verify if fabric is deployed. cisco.dcnm.dcnm_rest: @@ -231,7 +235,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional @@ -337,7 +341,7 @@ - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ interface_2 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/30 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/sanity.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/sanity.yaml index 46def14db..5b31ecd11 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/sanity.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/sanity.yaml @@ -1,49 +1,76 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# +# - A VXLAN_EVPN fabric +# +# switch_1 +# +# - A vrf-lite capable switch +# - Does not require an interface +# +# switch_2 +# +# - A vrf-lite capable switch +# - Does not require an interface +# +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" tags: - sanity - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" tags: - sanity -- name: SANITY- MERGED - Verify if fabric is deployed. +- name: SETUP.0 - SANITY - [with_items] print vars + ansible.builtin.debug: + var: item + with_items: + - "fabric_1 : {{ fabric_1 }}" + - "switch_1 : {{ switch_1 }}" + - "switch_2 : {{ switch_2 }}" + +- name: SETUP.1 - SANITY - Verify if fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" - register: result + register: result_setup_1 tags: sanity - assert: that: - - 'result.response.DATA != None' + - 'result_setup_1.response.DATA != None' tags: sanity -- name: SANITY- MERGED - Clean up any existing vrfs +- name: SETUP.2 - SANITY - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted tags: sanity -- name: SANITY- Pause for 20 seconds for NDFC to sync - ansible.builtin.pause: - seconds: 20 +- name: SETUP.3 - SANITY - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 tags: sanity ############################################### ### MERGED ## ############################################### -- name: SANITY- MERGED - Create, Attach and Deploy new VRF - VLAN Provided by the User - cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" +- name: TEST.1 - SANITY MERGED - [merged] Create, Attach, Deploy VLAN+VRF - VLAN Provided by the User + cisco.dcnm.dcnm_vrf: &conf1 + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -52,67 +79,77 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_1 tags: sanity -- name: SANITY- Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.1a - SANITY MERGED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_1a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_1a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 tags: sanity +- name: TEST.1b - SANITY MERGED - [debug] print result_1 + ansible.builtin.debug: + var: result_1 + tags: sanity + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_1.changed == true' + - 'result_1.response[0].RETURN_CODE == 200' + - 'result_1.response[1].RETURN_CODE == 200' + - 'result_1.response[2].RETURN_CODE == 200' + - '(result_1.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_1.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_1.diff[0].attach[0].deploy == true' + - 'result_1.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_1.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_1.diff[0].attach[1].ip_address' + - 'result_1.diff[0].vrf_name == "ansible-vrf-int1"' + tags: sanity + +- name: TEST.1c - SANITY MERGED - conf1 - Idempotence + cisco.dcnm.dcnm_vrf: *conf1 + register: result_1c tags: sanity -- name: SANITY- MERGED - conf1 - Idempotence - cisco.dcnm.dcnm_vrf: *conf - register: result +- name: TEST.1d - SANITY MERGED - [debug] print result_1c + ansible.builtin.debug: + var: result_1c tags: sanity - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_1c.changed == false' + - 'result_1c.response|length == 0' tags: sanity -- name: SANITY- MERGED - Clean up any existing vrfs +- name: TEST.1e - SANITY MERGED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted tags: sanity -- name: SANITY- Pause for 20 seconds for NDFC to sync - ansible.builtin.pause: - seconds: 20 +- name: TEST.1f - SANITY MERGED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 tags: sanity ############################################### ### REPLACED ## ############################################### -- name: SANITY- REPLACED - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: TEST.2 - SANITY REPLACED - [merged] Create, Attach, Deploy VLAN+VRF Provided by the User cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -121,39 +158,44 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_2 tags: sanity -- name: SANITY- Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.2a - SANITY REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_2a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_2a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 tags: sanity +- name: TEST.2b - SANITY REPLACED - [debug] print result_2 + ansible.builtin.debug: + var: result_2 + tags: sanity + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - tags: sanity - -- name: SANITY- REPLACED - Update existing VRF using replace - delete attachments - cisco.dcnm.dcnm_vrf: &conf1 - fabric: "{{ test_fabric }}" + - 'result_2.changed == true' + - 'result_2.response[0].RETURN_CODE == 200' + - 'result_2.response[1].RETURN_CODE == 200' + - 'result_2.response[2].RETURN_CODE == 200' + - '(result_2.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2.diff[0].attach[0].deploy == true' + - 'result_2.diff[0].attach[1].deploy == true' + - 'result_2.diff[0].vrf_name == "ansible-vrf-int1"' + tags: sanity + +- name: TEST.2c - SANITY REPLACED - [replaced] Update existing VRF - delete attachments + cisco.dcnm.dcnm_vrf: &conf2c + fabric: "{{ fabric_1 }}" state: replaced config: - vrf_name: ansible-vrf-int1 @@ -161,45 +203,60 @@ vrf_template: Default_VRF_Universal vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 - register: result + register: result_2c tags: sanity -- name: SANITY- Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.2d - SANITY REPLACED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 + tags: sanity + +- name: TEST.2e - SANITY REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_2e until: - - "query_result.response[0].parent.vrfStatus is search('NA')" + - "result_2e.response[0].parent.vrfStatus is search('NA')" retries: 30 delay: 2 tags: sanity +- name: TEST.2f - SANITY REPLACED - [debug] print result_2c + ansible.builtin.debug: + var: result_2c + tags: sanity + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == false' - - 'result.diff[0].attach[1].deploy == false' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_2c.changed == true' + - 'result_2c.response[0].RETURN_CODE == 200' + - 'result_2c.response[1].RETURN_CODE == 200' + - '(result_2c.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2c.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2c.diff[0].attach[0].deploy == false' + - 'result_2c.diff[0].attach[1].deploy == false' + - 'result_2c.diff[0].vrf_name == "ansible-vrf-int1"' tags: sanity -- name: SANITY- REPLACED - conf1 - Idempotence - cisco.dcnm.dcnm_vrf: *conf1 - register: result +- name: TEST.2g - SANITY REPLACED - conf2c - Idempotence + cisco.dcnm.dcnm_vrf: *conf2c + register: result_2g + tags: sanity + +- name: TEST.2h - SANITY REPLACED - [debug] print result_2g + ansible.builtin.debug: + var: result_2g tags: sanity - assert: that: - - 'result.changed == false' + - 'result_2g.changed == false' tags: sanity -- name: SANITY- REPLACED - Update existing VRF using replace - create attachments - cisco.dcnm.dcnm_vrf: &conf2 - fabric: "{{ test_fabric }}" +- name: TEST.2i - SANITY REPLACED - [replaced] Update existing VRF - create attachments + cisco.dcnm.dcnm_vrf: &conf2i + fabric: "{{ fabric_1 }}" state: replaced config: - vrf_name: ansible-vrf-int1 @@ -208,65 +265,80 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_2i tags: sanity -- name: SANITY- Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.2j - SANITY REPLACED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 + tags: sanity + +- name: TEST.2k - SANITY REPLACED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_2k until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_2k.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 tags: sanity +- name: TEST.2l - SANITY REPLACED - [debug] print result_2i + ansible.builtin.debug: + var: result_2i + tags: sanity + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - - 'result.diff[0].attach[0].vlan_id == 500' - - 'result.diff[0].attach[1].vlan_id == 500' + - 'result_2i.changed == true' + - 'result_2i.response[0].RETURN_CODE == 200' + - 'result_2i.response[1].RETURN_CODE == 200' + - '(result_2i.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2i.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2i.diff[0].attach[0].deploy == true' + - 'result_2i.diff[0].attach[1].deploy == true' + - 'result_2i.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_2i.diff[0].attach[0].vlan_id == 500' + - 'result_2i.diff[0].attach[1].vlan_id == 500' + tags: sanity + +- name: TEST.2m - SANITY REPLACED - [replaced] conf2i - Idempotence + cisco.dcnm.dcnm_vrf: *conf2i + register: result_2m tags: sanity -- name: SANITY- REPLACED - conf2 - Idempotence - cisco.dcnm.dcnm_vrf: *conf2 - register: result +- name: TEST.2n - SANITY REPLACED - [debug] print result_2m + ansible.builtin.debug: + var: result_2m tags: sanity - assert: that: - - 'result.changed == false' + - 'result_2m.changed == false' tags: sanity -- name: SANITY- REPLACED - Clean up any existing vrfs +- name: TEST.2o - SANITY REPLACED - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted tags: sanity -- name: SANITY- Pause for 20 seconds for NDFC to sync - ansible.builtin.pause: - seconds: 20 +- name: TEST.2p - SANITY REPLACED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 tags: sanity ############################################### ### OVERRIDDEN ## ############################################### -- name: SANITY- OVERRIDDEN - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: TEST.3 - SANITY OVERRIDDEN - [merged] Create, Attach, Deploy VLAN+VRF - Provided by the User cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -275,41 +347,46 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_3 tags: sanity -- name: SANITY- Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.3a - SANITY OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_3a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_3a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 tags: sanity +- name: TEST.3b - SANITY OVERRIDDEN - [debug] print result_3 + ansible.builtin.debug: + var: result_3 + tags: sanity + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - tags: sanity - -- name: SANITY- OVERRIDDEN - Update existing VRF using overridden - delete and create - cisco.dcnm.dcnm_vrf: &conf3 - fabric: "{{ test_fabric }}" + - 'result_3.changed == true' + - 'result_3.response[0].RETURN_CODE == 200' + - 'result_3.response[1].RETURN_CODE == 200' + - 'result_3.response[2].RETURN_CODE == 200' + - '(result_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3.diff[0].attach[0].deploy == true' + - 'result_3.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' + - 'result_3.diff[0].vrf_name == "ansible-vrf-int1"' + tags: sanity + +- name: TEST.3c - SANITY OVERRIDDEN - [overridden] Update existing VRF - delete and create + cisco.dcnm.dcnm_vrf: &conf3c + fabric: "{{ fabric_1 }}" state: overridden config: - vrf_name: ansible-vrf-int2 @@ -318,73 +395,83 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_3c tags: sanity -- name: SANITY- Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.3d - SANITY OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_3d until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_3d.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 tags: sanity +- name: TEST.3e - SANITY OVERRIDDEN - [debug] print result_3c + ansible.builtin.debug: + var: result_3c + tags: sanity + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - 'result.response[3].RETURN_CODE == 200' - - 'result.response[4].RETURN_CODE == 200' - - 'result.response[5].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - '(result.response[4].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[4].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int2"' - - 'result.diff[1].attach[0].deploy == false' - - 'result.diff[1].attach[1].deploy == false' - - 'result.diff[1].vrf_name == "ansible-vrf-int1"' - tags: sanity - -- name: SANITY- OVERRIDDEN - conf - Idempotence - cisco.dcnm.dcnm_vrf: *conf3 - register: result + - 'result_3c.changed == true' + - 'result_3c.response[0].RETURN_CODE == 200' + - 'result_3c.response[1].RETURN_CODE == 200' + - 'result_3c.response[2].RETURN_CODE == 200' + - 'result_3c.response[3].RETURN_CODE == 200' + - 'result_3c.response[4].RETURN_CODE == 200' + - 'result_3c.response[5].RETURN_CODE == 200' + - '(result_3c.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3c.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - '(result_3c.response[4].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3c.response[4].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3c.diff[0].attach[0].deploy == true' + - 'result_3c.diff[0].attach[1].deploy == true' + - 'result_3c.diff[0].vrf_name == "ansible-vrf-int2"' + - 'result_3c.diff[1].attach[0].deploy == false' + - 'result_3c.diff[1].attach[1].deploy == false' + - 'result_3c.diff[1].vrf_name == "ansible-vrf-int1"' + tags: sanity + +- name: TEST.3f - SANITY OVERRIDDEN - [overridden] conf3c - Idempotence + cisco.dcnm.dcnm_vrf: *conf3c + register: result_3f + tags: sanity + +- name: TEST.3g - SANITY OVERRIDDEN - [debug] print result_3f + ansible.builtin.debug: + var: result_3f tags: sanity - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_3f.changed == false' + - 'result_3f.response|length == 0' tags: sanity -- name: SANITY- OVERRIDDEN - Clean up any existing vrfs +- name: TEST.3h - SANITY OVERRIDDEN - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted tags: sanity -- name: SANITY- Pause for 20 seconds for NDFC to sync - ansible.builtin.pause: - seconds: 20 +- name: TEST.3i - SANITY OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 tags: sanity ############################################### ### QUERY ## ############################################### -- name: SANITY- QUERY - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: TEST.4 - SANITY QUERY - [merged] Create, Attach, Deploy VLAN+VRF - Provided by the User cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -393,41 +480,46 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_4 tags: sanity -- name: SANITY- Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.4a - SANITY QUERY - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_4a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_4a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 tags: sanity +- name: TEST.4b - SANITY QUERY - [debug] print result_4 + ansible.builtin.debug: + var: result_4 + tags: sanity + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - tags: sanity - -- name: SANITY- QUERY - Query the VRF + - 'result_4.changed == true' + - 'result_4.response[0].RETURN_CODE == 200' + - 'result_4.response[1].RETURN_CODE == 200' + - 'result_4.response[2].RETURN_CODE == 200' + - '(result_4.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_4.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_4.diff[0].attach[0].deploy == true' + - 'result_4.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_4.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_4.diff[0].attach[1].ip_address' + - 'result_4.diff[0].vrf_name == "ansible-vrf-int1"' + tags: sanity + +- name: TEST.4c - SANITY QUERY - [query] Query the VRF cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query config: - vrf_name: ansible-vrf-int1 @@ -436,45 +528,49 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_4c + tags: sanity + +- name: TEST.4d - SANITY QUERY - [debug] print result_4c + ansible.builtin.debug: + var: result_4c tags: sanity - assert: that: - - 'result.changed == false' - - 'result.response[0].parent.vrfName == "ansible-vrf-int1"' - - 'result.response[0].parent.vrfId == 9008011' - - 'result.response[0].parent.vrfStatus == "DEPLOYED"' - - 'result.response[0].attach[0].switchDetailsList[0].islanAttached == true' - - 'result.response[0].attach[0].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - - 'result.response[0].attach[0].switchDetailsList[0].vlan == 500' - - 'result.response[0].attach[1].switchDetailsList[0].islanAttached == true' - - 'result.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' - - 'result.response[0].attach[1].switchDetailsList[0].vlan == 500' - tags: sanity - -- name: SANITY- QUERY - Clean up existing vrfs + - 'result_4c.changed == false' + - 'result_4c.response[0].parent.vrfName == "ansible-vrf-int1"' + - 'result_4c.response[0].parent.vrfId == 9008011' + - 'result_4c.response[0].parent.vrfStatus == "DEPLOYED"' + - 'result_4c.response[0].attach[0].switchDetailsList[0].islanAttached == true' + - 'result_4c.response[0].attach[0].switchDetailsList[0].lanAttachedState == "DEPLOYED"' + - 'result_4c.response[0].attach[0].switchDetailsList[0].vlan == 500' + - 'result_4c.response[0].attach[1].switchDetailsList[0].islanAttached == true' + - 'result_4c.response[0].attach[1].switchDetailsList[0].lanAttachedState == "DEPLOYED"' + - 'result_4c.response[0].attach[1].switchDetailsList[0].vlan == 500' + tags: sanity + +- name: TEST.4e - SANITY QUERY - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted - register: result tags: sanity -- name: SANITY- Pause for 20 seconds for NDFC to sync - ansible.builtin.pause: - seconds: 20 +- name: TEST.4f - SANITY QUERY - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 tags: sanity ############################################### ### DELETED ## ############################################### -- name: SANITY- DELETED - Create, Attach and Deploy new VRF - VLAN Provided by the User +- name: TEST.5 - SANITY DELETED - Create, Attach, Deploy VLAN+VRF - Provided by the User cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -483,73 +579,88 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_5 tags: sanity -- name: SANITY- Query fabric state until vrfStatus transitions to DEPLOYED state +- name: TEST.5a - SANITY DELETED - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_5a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_5a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 tags: sanity +- name: TEST.5b - SANITY DELETED - [debug] print result_5 + ansible.builtin.debug: + var: result_5 + tags: sanity + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - tags: sanity - -- name: SANITY- DELETED - Delete VRF using deleted state - cisco.dcnm.dcnm_vrf: &conf4 - fabric: "{{ test_fabric }}" + - 'result_5.changed == true' + - 'result_5.response[0].RETURN_CODE == 200' + - 'result_5.response[1].RETURN_CODE == 200' + - 'result_5.response[2].RETURN_CODE == 200' + - '(result_5.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_5.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_5.diff[0].attach[0].deploy == true' + - 'result_5.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_5.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_5.diff[0].attach[1].ip_address' + - 'result_5.diff[0].vrf_name == "ansible-vrf-int1"' + tags: sanity + +- name: TEST.5c - SANITY DELETED - [deleted] Delete the VRF + cisco.dcnm.dcnm_vrf: &conf5c + fabric: "{{ fabric_1 }}" state: deleted config: - vrf_name: ansible-vrf-int1 vrf_id: 9008011 vrf_template: Default_VRF_Universal vrf_extension_template: Default_VRF_Extension_Universal - register: result + register: result_5c + tags: sanity + +- name: TEST.5d - SANITY DELETED - [debug] print result_5c + ansible.builtin.debug: + var: result_5c tags: sanity - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[1].MESSAGE == "OK"' - - 'result.response[2].RETURN_CODE == 200' - - 'result.response[2].METHOD == "DELETE"' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == false' - - 'result.diff[0].attach[1].deploy == false' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_5c.changed == true' + - 'result_5c.response[0].RETURN_CODE == 200' + - 'result_5c.response[1].RETURN_CODE == 200' + - 'result_5c.response[1].MESSAGE == "OK"' + - 'result_5c.response[2].RETURN_CODE == 200' + - 'result_5c.response[2].METHOD == "DELETE"' + - '(result_5c.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_5c.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_5c.diff[0].attach[0].deploy == false' + - 'result_5c.diff[0].attach[1].deploy == false' + - 'result_5c.diff[0].vrf_name == "ansible-vrf-int1"' + tags: sanity + +- name: TEST.5e - SANITY DELETED - conf5c - Idempotence + cisco.dcnm.dcnm_vrf: *conf5c + register: result_5e tags: sanity -- name: SANITY- DELETED - conf - Idempotence - cisco.dcnm.dcnm_vrf: *conf4 - register: result +- name: TEST.5f - SANITY DELETED - [debug] print result_5e + ansible.builtin.debug: + var: result_5e tags: sanity - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_5e.changed == false' + - 'result_5e.response|length == 0' + - 'result_5e.diff|length == 0' tags: sanity - - 'result.diff|length == 0' diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/deleted_vrf_all.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/deleted_vrf_all.yaml index 16f4222ec..5748884fc 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/deleted_vrf_all.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/deleted_vrf_all.yaml @@ -1,13 +1,32 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# +# - A VXLAN_EVPN fabric +# +# switch_1 +# +# - A vrf-capable switch +# - Does not require an interface +# +# switch_2 +# +# - A vrf-capable switch +# - Does not require an interface +# +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" - name: DELETE_ALL - Verify if fabric is deployed. @@ -22,7 +41,7 @@ - name: DELETE_ALL - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted ############################################### @@ -31,7 +50,7 @@ - name: DELETE_ALL - Create, Attach and Deploy new VRF with all values cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -55,14 +74,14 @@ bgp_password: "74657374" bgp_passwd_encrypt: 7 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -80,13 +99,13 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" or "{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" or "{{ ansible_switch1 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" or "{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" or "{{ switch_1 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - name: DELETE_ALL - Clean existing vrfs cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted register: result @@ -98,8 +117,8 @@ - 'result.response[2].RETURN_CODE == 200' - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - '"{{ ansible_switch1 }}" or "{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" or "{{ ansible_switch1 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" or "{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" or "{{ switch_1 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - name: DELETE_ALL - conf - Idempotence @@ -117,5 +136,5 @@ - name: DELETE_ALL - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/merged_vrf_all.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/merged_vrf_all.yaml index 0f3f69c87..733e3d0a4 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/merged_vrf_all.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/merged_vrf_all.yaml @@ -1,13 +1,32 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# +# - A VXLAN_EVPN fabric +# +# switch_1 +# +# - A vrf-capable switch +# - Does not require an interface +# +# switch_2 +# +# - A vrf-capable switch +# - Does not require an interface +# +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" - name: MERGED_ALL - Verify if fabric is deployed. @@ -22,7 +41,7 @@ - name: MERGED_ALL - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted ############################################### @@ -31,7 +50,7 @@ - name: MERGED_ALL - Create, Attach and Deploy new VRF with all values cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -55,14 +74,14 @@ bgp_password: "74657374" bgp_passwd_encrypt: 7 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -80,8 +99,8 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" or "{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" or "{{ ansible_switch1 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" or "{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" or "{{ switch_1 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - name: MERGED_ALL - conf - Idempotence @@ -99,5 +118,5 @@ - name: MERGED_ALL - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/overridden_vrf_all.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/overridden_vrf_all.yaml index f5d518ba1..fd7cb618a 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/overridden_vrf_all.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/overridden_vrf_all.yaml @@ -1,13 +1,32 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# +# - A VXLAN_EVPN fabric +# +# switch_1 +# +# - A vrf-capable switch +# - Does not require an interface +# +# switch_2 +# +# - A vrf-capable switch +# - Does not require an interface +# +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" - name: OVERRIDDEN_ALL - Verify if fabric is deployed. @@ -22,7 +41,7 @@ - name: OVERRIDDEN_ALL - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted ############################################### @@ -31,7 +50,7 @@ - name: OVERRIDDEN_ALL - Create, Attach and Deploy new VRF with all values cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -55,14 +74,14 @@ bgp_password: "74657374" bgp_passwd_encrypt: 7 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -80,8 +99,8 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" or "{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" or "{{ ansible_switch1 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" or "{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" or "{{ switch_1 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - name: OVERRIDDEN_ALL - conf - Idempotence @@ -95,7 +114,7 @@ - name: OVERRIDDEN_ALL - Override a existing VRF with a new one cisco.dcnm.dcnm_vrf: &conf1 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: overridden config: - vrf_name: ansible-vrf-int2 @@ -119,14 +138,14 @@ bgp_password: "74657374" bgp_passwd_encrypt: 7 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -151,10 +170,10 @@ - 'result.diff[0].attach[1].deploy == true' - 'result.diff[1].attach[0].deploy == false' - 'result.diff[1].attach[1].deploy == false' - - '"{{ ansible_switch1 }}" or "{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" or "{{ ansible_switch1 }}" in result.diff[0].attach[1].ip_address' - - '"{{ ansible_switch1 }}" or "{{ ansible_switch2 }}" in result.diff[1].attach[0].ip_address' - - '"{{ ansible_switch2 }}" or "{{ ansible_switch1 }}" in result.diff[1].attach[1].ip_address' + - '"{{ switch_1 }}" or "{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" or "{{ switch_1 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" or "{{ switch_2 }}" in result.diff[1].attach[0].ip_address' + - '"{{ switch_2 }}" or "{{ switch_1 }}" in result.diff[1].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int2"' - 'result.diff[1].vrf_name == "ansible-vrf-int1"' @@ -173,5 +192,5 @@ - name: OVERRIDDEN_ALL - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/replaced_vrf_all.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/replaced_vrf_all.yaml index 010bca5d8..7645e5f00 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/replaced_vrf_all.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/replaced_vrf_all.yaml @@ -1,13 +1,32 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# +# - A VXLAN_EVPN fabric +# +# switch_1 +# +# - A vrf-capable switch +# - Does not require an interface +# +# switch_2 +# +# - A vrf-capable switch +# - Does not require an interface +# +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" - name: REPLACED_ALL - Verify if fabric is deployed. @@ -22,7 +41,7 @@ - name: REPLACED_ALL - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted ############################################### @@ -31,7 +50,7 @@ - name: REPLACED_ALL - Create, Attach and Deploy new VRF with all values cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -55,14 +74,14 @@ bgp_password: "74657374" bgp_passwd_encrypt: 7 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -80,8 +99,8 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" or "{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" or "{{ ansible_switch1 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_1 }}" or "{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" or "{{ switch_1 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - name: REPLACED_ALL - conf - Idempotence @@ -95,7 +114,7 @@ - name: REPLACED_ALL - Replace VRF with all values cisco.dcnm.dcnm_vrf: &conf1 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: replaced config: - vrf_name: ansible-vrf-int1 @@ -119,14 +138,14 @@ bgp_password: "74657374" bgp_passwd_encrypt: 7 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + - ip_address: "{{ switch_2 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -156,5 +175,5 @@ - name: REPLACED_ALL - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml index 76b82792c..17e6ef2fe 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml @@ -1,13 +1,22 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# +# - A VXLAN_EVPN fabric +# +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" - name: SCALE - Verify if fabric is deployed. @@ -22,7 +31,7 @@ - name: SCALE - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted - name: Dummy set fact for leaf_attach_list @@ -36,14 +45,14 @@ - name: Push all VRFs to DCNM cisco.dcnm.dcnm_vrf: - fabric: '{{ test_fabric }}' + fabric: '{{ fabric_1 }}' state: merged config: '{{ vrfs_list }}' register: result - name: SCALE - Clean up existing vrfs cisco.dcnm.dcnm_vrf: &conf - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted - name: SCALE - conf - Idempotence @@ -62,5 +71,5 @@ - name: SCALE - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/vrf_lite.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/vrf_lite.yaml index 6ad41f906..38bb637fe 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/vrf_lite.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/vrf_lite.yaml @@ -1,13 +1,46 @@ +############################################## +## REQUIRED VARS ## +############################################## +# fabric_1 +# A VXLAN_EVPN fabric +# +# switch_1 +# +# - A vrf-lite capable switch +# +# switch_2 +# +# - A vrf-lite capable switch +# +# switch_3 +# +# - A vrf capable switch +# - switch_3 does not require any interfaces +# +# interface_1a +# +# - 1st interface on switch_1 +# +# interface_2a +# +# - 1st interface on switch_2 +# +# interface_2b +# +# - 2nd interface on switch_2 +# +############################################## + ############################################## ## SETUP ## ############################################## - set_fact: - rest_path: "/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/rest/control/fabrics/{{ fabric_1 }}" when: controller_version == "11" - set_fact: - rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" - name: MERGED - Verify if fabric is deployed. @@ -22,7 +55,7 @@ - name: MERGED - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted - name: VRF LITE - sleep for 40 seconds for DCNM to completely update the state @@ -38,7 +71,7 @@ - name: VRF LITE- Create, Attach and Deploy new VRF - VLAN/VRF LITE EXTENSION Provided by the User in one switch cisco.dcnm.dcnm_vrf: &conf1 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -47,22 +80,22 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/24 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm + - ip_address: "{{ switch_3 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -80,10 +113,10 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_3 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - - '"{{ ansible_int1 }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' + - '"{{ interface_2a }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"ansible-vrf-int1" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"10.33.0.2/24" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' @@ -98,7 +131,7 @@ - name: VRF LITE- Attach and Deploy second VRF LITE EXTENSION Provided by the User in one switch cisco.dcnm.dcnm_vrf: &conf2 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int1 @@ -107,29 +140,29 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/24 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int2 }}" # mandatory + interface: "{{ interface_2b }}" # mandatory ipv4_addr: 20.33.0.2/24 # optional neighbor_ipv4: 20.33.0.1 # optional ipv6_addr: 3010::10:34:0:7/64 # optional neighbor_ipv6: 3010::10:34:0:3 # optional dot1q: 21 # dot1q can be got from dcnm - - ip_address: "{{ ansible_switch1 }}" + - ip_address: "{{ switch_3 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -144,12 +177,12 @@ - 'result.response[1].RETURN_CODE == 200' - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[0].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - - '"{{ ansible_int1 }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' + - '"{{ interface_2a }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"ansible-vrf-int1" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"10.33.0.2/24" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - - '"{{ ansible_int2 }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' + - '"{{ interface_2b }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"20.33.0.2/24" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - name: VRF LITE - conf2 - Idempotence @@ -163,7 +196,7 @@ - name: VRF LITE- Replace VRF LITE Attachment and Deploy by the User in one switch cisco.dcnm.dcnm_vrf: &conf3 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: replaced config: - vrf_name: ansible-vrf-int1 @@ -172,22 +205,22 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 500 attach: - - ip_address: "{{ ansible_switch1 }}" - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/24 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm + - ip_address: "{{ switch_3 }}" deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -202,9 +235,9 @@ - 'result.response[1].RETURN_CODE == 200' - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[0].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - - '"{{ ansible_int1 }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' + - '"{{ interface_2a }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"ansible-vrf-int1" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"10.33.0.2/24" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' @@ -219,7 +252,7 @@ - name: VRF LITE- Override VRF and VRF LITE EXTENSION Provided by the User cisco.dcnm.dcnm_vrf: &conf4 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: overridden config: - vrf_name: ansible-vrf-int2 @@ -228,17 +261,17 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 400 attach: - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/24 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int2 }}" # mandatory + interface: "{{ interface_2b }}" # mandatory ipv4_addr: 20.33.0.2/24 # optional neighbor_ipv4: 20.33.0.1 # optional ipv6_addr: 3010::10:34:0:7/64 # optional @@ -249,7 +282,7 @@ - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -271,15 +304,15 @@ - 'result.diff[0].attach[0].deploy == true' - 'result.diff[1].attach[0].deploy == false' - 'result.diff[1].attach[1].deploy == false' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch1 }}" in result.diff[1].attach[0].ip_address' - - '"{{ ansible_switch2 }}" in result.diff[1].attach[1].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_3 }}" in result.diff[1].attach[0].ip_address' + - '"{{ switch_2 }}" in result.diff[1].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int2"' - 'result.diff[1].vrf_name == "ansible-vrf-int1"' - - '"{{ ansible_int1 }}" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' + - '"{{ interface_2a }}" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' - '"ansible-vrf-int1" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' - '"10.33.0.2/24" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' - - '"{{ ansible_int2 }}" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' + - '"{{ interface_2b }}" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' - '"20.33.0.2/24" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' - name: VRF LITE - conf4 - Idempotence @@ -293,7 +326,7 @@ - name: VRF LITE - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted - name: VRF LITE - sleep for 40 seconds for DCNM to completely update the state @@ -305,7 +338,7 @@ - name: VRF LITE- Create, Attach and Deploy new VRF - VLAN/VRF LITE EXTENSION Provided by the User in multiple switch cisco.dcnm.dcnm_vrf: &conf5 - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: merged config: - vrf_name: ansible-vrf-int2 @@ -314,37 +347,37 @@ vrf_extension_template: Default_VRF_Extension_Universal vlan_id: 400 attach: - - ip_address: "{{ ansible_switch2 }}" + - ip_address: "{{ switch_1 }}" + vrf_lite: + - peer_vrf: ansible-vrf-int3 # optional + interface: "{{ interface_1a }}" # mandatory + ipv4_addr: 40.33.0.2/24 # optional + neighbor_ipv4: 40.33.0.1 # optional + ipv6_addr: 5010::10:34:0:7/64 # optional + neighbor_ipv6: 5010::10:34:0:3 # optional + dot1q: 4 # dot1q can be got from dcnm + - ip_address: "{{ switch_2 }}" vrf_lite: - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int1 }}" # mandatory + interface: "{{ interface_2a }}" # mandatory ipv4_addr: 10.33.0.2/24 # optional neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional dot1q: 2 # dot1q can be got from dcnm - peer_vrf: ansible-vrf-int1 # optional - interface: "{{ ansible_int2 }}" # mandatory + interface: "{{ interface_2b }}" # mandatory ipv4_addr: 20.33.0.2/24 # optional neighbor_ipv4: 20.33.0.1 # optional ipv6_addr: 3010::10:34:0:7/64 # optional neighbor_ipv6: 3010::10:34:0:3 # optional dot1q: 21 # dot1q can be got from dcnm - - ip_address: "{{ ansible_switch3 }}" - vrf_lite: - - peer_vrf: ansible-vrf-int3 # optional - interface: "{{ ansible_int3 }}" # mandatory - ipv4_addr: 40.33.0.2/24 # optional - neighbor_ipv4: 40.33.0.1 # optional - ipv6_addr: 5010::10:34:0:7/64 # optional - neighbor_ipv6: 5010::10:34:0:3 # optional - dot1q: 4 # dot1q can be got from dcnm deploy: true register: result - name: Query fabric state until vrfStatus transitions to DEPLOYED state cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: query register: query_result until: @@ -362,16 +395,16 @@ - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - 'result.diff[0].attach[0].deploy == true' - 'result.diff[0].attach[1].deploy == true' - - '"{{ ansible_switch2 }}" in result.diff[0].attach[0].ip_address' - - '"{{ ansible_switch3 }}" in result.diff[0].attach[1].ip_address' + - '"{{ switch_2 }}" in result.diff[0].attach[0].ip_address' + - '"{{ switch_1 }}" in result.diff[0].attach[1].ip_address' - 'result.diff[0].vrf_name == "ansible-vrf-int2"' - - '"{{ ansible_int3 }}" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' + - '"{{ interface_1a }}" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' - '"ansible-vrf-int3" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' - '"40.33.0.2/24" in query_result.response[0].attach[0].switchDetailsList[0].extensionValues' - - '"{{ ansible_int1 }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' + - '"{{ interface_2a }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"ansible-vrf-int1" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"10.33.0.2/24" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - - '"{{ ansible_int2 }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' + - '"{{ interface_2b }}" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - '"20.33.0.2/24" in query_result.response[0].attach[1].switchDetailsList[0].extensionValues' - name: VRF LITE - conf5 - Idempotence @@ -389,5 +422,5 @@ - name: VRF LITE - Clean up any existing vrfs cisco.dcnm.dcnm_vrf: - fabric: "{{ test_fabric }}" + fabric: "{{ fabric_1 }}" state: deleted From ecd184941289e592b29f408a75f189a277d26646 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 11 Dec 2024 16:43:43 -1000 Subject: [PATCH 44/49] IT: Update scale.yaml 1. Use standardized task titles 2. Print results prior to each assert --- .../dcnm/self-contained-tests/scale.yaml | 68 +++++++++++++------ 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml index 17e6ef2fe..f9a25fc74 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml @@ -19,57 +19,85 @@ rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}" when: controller_version >= "12" -- name: SCALE - Verify if fabric is deployed. +- name: SETUP.0 - SCALE - [with_items] print vars + ansible.builtin.debug: + var: item + with_items: + - "fabric_1 : {{ fabric_1 }}" + +- name: SETUP.1 - SCALE - Verify if fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" - register: result + register: setup_result_1 - assert: that: - - 'result.response.DATA != None' + - 'setup_result_1.response.DATA != None' -- name: SCALE - Clean up any existing vrfs +- name: SETUP.2 - SCALE - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted + register: setup_result_2 + tags: sanity -- name: Dummy set fact for leaf_attach_list +- name: SETUP.3 - SCALE - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 + when: setup_result_2.changed == false + tags: sanity + +- name: SETUP.4 - SCALE - [set_fact] Dummy set fact for leaf_attach_list set_fact: leaf_vrf_attach: [] -- name: Build list of VRFs to be deployed +- name: SETUP.5 - SCALE - [set_fact] Build list of VRFs to be deployed set_fact: vrfs_list: "{{ vrfs_list|default([]) + [{ 'vrf_name': 'TEST_VRF%03d' | format(item), 'deploy': 'no', 'vrf_id': (item | int + 50000) | int, 'vlan_id': (item | int + 2000) | int, 'attach': leaf_vrf_attach }] }}" loop: '{{ range(0, 800) | list }}' -- name: Push all VRFs to DCNM +- name: TEST.1 - SCALE - [merged] Push all VRFs to the controller cisco.dcnm.dcnm_vrf: fabric: '{{ fabric_1 }}' state: merged config: '{{ vrfs_list }}' register: result -- name: SCALE - Clean up existing vrfs - cisco.dcnm.dcnm_vrf: &conf +- name: TEST.1a - SCALE - [wait_for] Wait 60 seconds + wait_for: + timeout: 60 + tags: sanity + +- name: TEST.2 - SCALE - [deleted] Delete all VRFs + cisco.dcnm.dcnm_vrf: &conf2 fabric: "{{ fabric_1 }}" state: deleted + register: result_2 -- name: SCALE - conf - Idempotence - cisco.dcnm.dcnm_vrf: *conf - register: result +- name: TEST.2a - SCALE - [debug] print result_2 + ansible.builtin.debug: + var: result_2 + +- assert: + that: + - 'result_2.changed == true' + +- name: TEST.2b - SCALE - [deleted] conf2 - Idempotence + cisco.dcnm.dcnm_vrf: *conf2 + register: result_2b + +- name: TEST.2c - SCALE - [debug] print result_2b + ansible.builtin.debug: + var: result_2b - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' - - 'result.diff|length == 0' + - 'result_2b.changed == false' + - 'result_2b.response|length == 0' + - 'result_2b.diff|length == 0' ################################################ #### CLEAN-UP ## ################################################ - -- name: SCALE - Clean up any existing vrfs - cisco.dcnm.dcnm_vrf: - fabric: "{{ fabric_1 }}" - state: deleted +# No CLEANUP required From a0f44c37e4a3b7426c0dff57481587fc7a463601 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 11 Dec 2024 16:46:07 -1000 Subject: [PATCH 45/49] dcnm_vrf: IT dynamic_inventory.py small modifications 1. dcnm_vrf: use switch_1, switch_2, switch_3 directly 2. Add scale role to the 'if nd_role' conditional --- playbooks/files/dynamic_inventory.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/playbooks/files/dynamic_inventory.py b/playbooks/files/dynamic_inventory.py index a5abf16f7..6f63bffc4 100755 --- a/playbooks/files/dynamic_inventory.py +++ b/playbooks/files/dynamic_inventory.py @@ -133,8 +133,7 @@ # Placeholders if you'd rather directly set each of # the switch vars instead of setting the switch vars -# from the switch roles above (as is done for dcnm_vrf -# below). +# from the switch roles above. switch_1 = environ.get("ND_SWITCH_1_IP4", "10.1.1.112") switch_2 = environ.get("ND_SWITCH_2_IP4", "10.1.1.113") switch_3 = environ.get("ND_SWITCH_3_IP4", "10.1.1.108") @@ -146,21 +145,19 @@ interface_3a = environ.get("ND_INTERFACE_3a", "Ethernet1/3") if nd_role == "dcnm_vrf": + pass # VXLAN/EVPN Fabric Name # fabric_1 # - all tests # switch_1 # - all tests # - vrf capable - switch_1 = spine_1 # switch_2 # - all tests # - vrf-lite capable - switch_2 = spine_2 # switch_3 # - merged # - NOT vrf-lite capable - switch_3 = leaf_3 # interface_1a # - no tests # interface_2a @@ -182,6 +179,8 @@ switch_2 = spine_2 # switch_3: vrf capable switch_3 = bgw_1 +elif nd_role == "scale": + pass else: switch_1 = leaf_1 switch_2 = spine_1 From 41a8fd7301bfa282cd9e55db3dea4cae24556e2b Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sat, 14 Dec 2024 20:08:37 -1000 Subject: [PATCH 46/49] dcnm_vrf: fix for #356, and for an undeploy case, simplify, more... 1. Fix case where previous commit in this PR broke undeploy. 2. Fix for issue #356 2. Update unit tests to align with changes in this commit 3. Some simplifications, including - Add a method send_to_controller() to aggregate POST, PUT, DELETE verb handling. This method calls dcnm_send() and then calls the response handler, etc. This removes duplicated code throughout the module. - Refactor vrf_lite handlng out of update_attach_params() and into new method update_attach_params_extension_values() - Never noticed this, but it appears we don't have to use inspect() with the new logging system, except in cases where fail_json() is called. Removed inspect() from all methods that do not call fail_json() - New method is_border_switch() to remove this code from push_diff_attach() and for future consolidation into a shared library. - Move dcnm_vrf_paths dictionary out of the class. These endpoints will later be moved to common/api/ep/. - in __init__(), add self.sn_ip, built from self.ip_sn. There were several case where the module wanted a serial_number given an ip_address. Added two methods that leverage self.sn_ip and self.ip_sn: - self.serial_number_to_ip() - self.ip_to_serial_number() Replaced all instances where duplicated code was performing these functions. --- plugins/modules/dcnm_vrf.py | 1338 ++++++++++------- .../targets/dcnm_vrf/tests/dcnm/merged.yaml | 6 +- .../unit/modules/dcnm/fixtures/dcnm_vrf.json | 30 + tests/unit/modules/dcnm/test_dcnm_vrf.py | 16 +- 4 files changed, 834 insertions(+), 556 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 664672382..b7158fdd9 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -570,59 +570,76 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import ( - dcnm_get_ip_addr_info, dcnm_get_url, dcnm_send, dcnm_version_supported, - get_fabric_details, get_fabric_inventory_details, get_ip_sn_dict, - get_ip_sn_fabric_dict, validate_list_of_dicts) + dcnm_get_url, dcnm_send, dcnm_version_supported, get_fabric_details, + get_fabric_inventory_details, get_ip_sn_dict, get_ip_sn_fabric_dict, + validate_list_of_dicts) from ..module_utils.common.log_v2 import Log +dcnm_vrf_paths = { + 11: { + "GET_VRF": "/rest/top-down/fabrics/{}/vrfs", + "GET_VRF_ATTACH": "/rest/top-down/fabrics/{}/vrfs/attachments?vrf-names={}", + "GET_VRF_SWITCH": "/rest/top-down/fabrics/{}/vrfs/switches?vrf-names={}&serial-numbers={}", + "GET_VRF_ID": "/rest/managed-pool/fabrics/{}/partitions/ids", + "GET_VLAN": "/rest/resource-manager/vlan/{}?vlanUsageType=TOP_DOWN_VRF_VLAN", + }, + 12: { + "GET_VRF": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{}/vrfs", + "GET_VRF_ATTACH": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{}/vrfs/attachments?vrf-names={}", + "GET_VRF_SWITCH": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{}/vrfs/switches?vrf-names={}&serial-numbers={}", + "GET_VRF_ID": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{}/vrfinfo", + "GET_VLAN": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/resource-manager/vlan/{}?vlanUsageType=TOP_DOWN_VRF_VLAN", + }, +} -class DcnmVrf: - dcnm_vrf_paths = { - 11: { - "GET_VRF": "/rest/top-down/fabrics/{}/vrfs", - "GET_VRF_ATTACH": "/rest/top-down/fabrics/{}/vrfs/attachments?vrf-names={}", - "GET_VRF_SWITCH": "/rest/top-down/fabrics/{}/vrfs/switches?vrf-names={}&serial-numbers={}", - "GET_VRF_ID": "/rest/managed-pool/fabrics/{}/partitions/ids", - "GET_VLAN": "/rest/resource-manager/vlan/{}?vlanUsageType=TOP_DOWN_VRF_VLAN", - }, - 12: { - "GET_VRF": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{}/vrfs", - "GET_VRF_ATTACH": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{}/vrfs/attachments?vrf-names={}", - "GET_VRF_SWITCH": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{}/vrfs/switches?vrf-names={}&serial-numbers={}", - "GET_VRF_ID": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{}/vrfinfo", - "GET_VLAN": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/resource-manager/vlan/{}?vlanUsageType=TOP_DOWN_VRF_VLAN", - }, - } +class DcnmVrf: def __init__(self, module): self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") self.module = module self.params = module.params + self.state = self.params.get("state") + + msg = f"self.state: {self.state}, " + msg += "self.params: " + msg += f"{json.dumps(self.params, indent=4, sort_keys=True)}" + self.log.debug(msg) + self.fabric = module.params["fabric"] self.config = copy.deepcopy(module.params.get("config")) + + msg = f"self.state: {self.state}, " + msg += "self.config: " + msg += f"{json.dumps(self.config, indent=4, sort_keys=True)}" + self.log.debug(msg) + self.check_mode = False self.have_create = [] self.want_create = [] self.diff_create = [] self.diff_create_update = [] - # This variable is created specifically to hold all the create payloads which are missing a - # vrfId. These payloads are sent to DCNM out of band (basically in the get_diff_merge()) - # We lose diffs for these without this variable. The content stored here will be helpful for - # cases like "check_mode" and to print diffs[] in the output of each task. + # self.diff_create_quick holds all the create payloads which are + # missing a vrfId. These payloads are sent to DCNM out of band + # (in the get_diff_merge()). We lose diffs for these without this + # variable. The content stored here will be helpful for cases like + # "check_mode" and to print diffs[] in the output of each task. self.diff_create_quick = [] self.have_attach = [] self.want_attach = [] self.diff_attach = [] self.validated = [] - # diff_detach is to list all attachments of a vrf being deleted, especially for state: OVERRIDDEN - # The diff_detach and delete operations have to happen before create+attach+deploy for vrfs being created. - # This is specifically to address cases where VLAN from a vrf which is being deleted is used for another - # vrf. Without this additional logic, the create+attach+deploy go out first and complain the VLAN is already - # in use. + # diff_detach contains all attachments of a vrf being deleted, + # especially for state: OVERRIDDEN + # The diff_detach and delete operations have to happen before + # create+attach+deploy for vrfs being created. This is to address + # cases where VLAN from a vrf which is being deleted is used for + # another vrf. Without this additional logic, the create+attach+deploy + # go out first and complain the VLAN is already in use. self.diff_detach = [] self.have_deploy = {} self.want_deploy = {} @@ -634,13 +651,14 @@ def __init__(self, module): self.dcnm_version = dcnm_version_supported(self.module) self.inventory_data = get_fabric_inventory_details(self.module, self.fabric) self.ip_sn, self.hn_sn = get_ip_sn_dict(self.inventory_data) + self.sn_ip = {value: key for (key, value) in self.ip_sn.items()} self.fabric_data = get_fabric_details(self.module, self.fabric) self.fabric_type = self.fabric_data.get("fabricType") self.ip_fab, self.sn_fab = get_ip_sn_fabric_dict(self.inventory_data) if self.dcnm_version > 12: - self.paths = self.dcnm_vrf_paths[12] + self.paths = dcnm_vrf_paths[12] else: - self.paths = self.dcnm_vrf_paths[self.dcnm_version] + self.paths = dcnm_vrf_paths[self.dcnm_version] self.result = {"changed": False, "diff": [], "response": []} @@ -657,7 +675,7 @@ def __init__(self, module): "PEER_VRF_NAME", ] - msg = f"{self.class_name}.__init__(): DONE" + msg = "DONE" self.log.debug(msg) @staticmethod @@ -720,12 +738,10 @@ def to_bool(self, key, dict_with_key): if value in ["true", "True", True]: return True - method_name = inspect.stack()[0][3] caller = inspect.stack()[1][3] - msg = f"{self.class_name}.{method_name}: " - msg += f"caller: {caller}: " - msg = f"{str(value)} with type {type(value)} " + msg = f"caller: {caller}: " + msg += f"{str(value)} with type {type(value)} " msg += "is not convertable to boolean" self.module.fail_json(msg=msg) @@ -743,9 +759,21 @@ def compare_properties(dict1, dict2, property_list): return True def diff_for_attach_deploy(self, want_a, have_a, replace=False): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED " - msg += f"with replace == {replace}" + """ + # Summary + + Return attach_list, deploy_vrf + + Where: + + - attach list is a list of attachment differences + - deploy_vrf is a boolean + + ## Raises + + None + """ + msg = f"ENTERED with replace == {replace}" self.log.debug(msg) attach_list = [] @@ -804,8 +832,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): and have["extensionValues"] != "" ): - msg = f"{self.class_name}.{method_name}: " - msg += "want[extensionValues] != '' and have[extensionValues] != ''" + msg = "want[extensionValues] != '' and have[extensionValues] != ''" self.log.debug(msg) want_ext_values = want["extensionValues"] @@ -863,9 +890,19 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): found = True else: found = True + msg = f"want_is_deploy: {str(want.get('want_is_deploy'))}, " + msg += f"have_is_deploy: {str(want.get('have_is_deploy'))}" + self.log.debug(msg) + want_is_deploy = self.to_bool("is_deploy", want) have_is_deploy = self.to_bool("is_deploy", have) + msg = f"want_is_attached: {str(want.get('want_is_attached'))}, " + msg += ( + f"want_is_attached: {str(want.get('want_is_attached'))}" + ) + self.log.debug(msg) + want_is_attached = self.to_bool("isAttached", want) have_is_attached = self.to_bool("isAttached", have) @@ -882,6 +919,14 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): deploy_vrf = True continue + msg = ( + f"want_deployment: {str(want.get('want_deployment'))}, " + ) + msg += ( + f"have_deployment: {str(want.get('have_deployment'))}" + ) + self.log.debug(msg) + want_deployment = self.to_bool("deployment", want) have_deployment = self.to_bool("deployment", have) @@ -892,10 +937,8 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): deploy_vrf = True if self.dict_values_differ(want_inst_values, have_inst_values): - msg = f"{self.class_name}.{method_name}: " - msg += "dict values differ. Set found = False" + msg = "dict values differ. Set found = False" self.log.debug(msg) - found = False if found: @@ -905,6 +948,10 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): break if not found: + msg = f"isAttached: {str(want.get('isAttached'))}, " + msg += f"is_deploy: {str(want.get('is_deploy'))}" + self.log.debug(msg) + if self.to_bool("isAttached", want): del want["isAttached"] want["deployment"] = True @@ -912,115 +959,193 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if self.to_bool("is_deploy", want): deploy_vrf = True - msg = f"{self.class_name}.{method_name}: Returning " + msg = "Returning " msg += f"deploy_vrf: {deploy_vrf}, " msg += "attach_list: " msg += f"{json.dumps(attach_list, indent=4, sort_keys=True)}" self.log.debug(msg) return attach_list, deploy_vrf - def update_attach_params(self, attach, vrf_name, deploy, vlan_id): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + def update_attach_params_extension_values(self, attach) -> dict: + """ + # Summary - vrf_ext = False + Given an attachment object (see example below): - if not attach: + - Return a populated extension_values dictionary + if the attachment object's vrf_lite parameter is + not null. + - Return an empty dictionary if the attachment object's + vrf_lite parameter is null. + + ## Raises + + Calls fail_json() if the vrf_lite parameter is not null + and the role of the switch in the attachment object is not + one of the various border roles. + + ## Example attach object + + - extensionValues content removed for brevity + - instanceValues content removed for brevity + + ```json + { + "deployment": true, + "export_evpn_rt": "", + "extensionValues": "{}", + "fabric": "f1", + "freeformConfig": "", + "import_evpn_rt": "", + "instanceValues": "{}", + "isAttached": true, + "is_deploy": true, + "serialNumber": "FOX2109PGCS", + "vlan": 500, + "vrfName": "ansible-vrf-int1", + "vrf_lite": [ + { + "dot1q": 2, + "interface": "Ethernet1/2", + "ipv4_addr": "10.33.0.2/30", + "ipv6_addr": "2010::10:34:0:7/64", + "neighbor_ipv4": "10.33.0.1", + "neighbor_ipv6": "2010::10:34:0:3", + "peer_vrf": "ansible-vrf-int1" + } + ] + } + ``` + + """ + method_name = inspect.stack()[0][3] + self.log.debug("ENTERED") + + if not attach["vrf_lite"]: + msg = "Early return. No vrf_lite extensions to process." + self.log.debug(msg) return {} - serial = "" - attach["ip_address"] = dcnm_get_ip_addr_info( - self.module, attach["ip_address"], None, None - ) - for ip, ser in self.ip_sn.items(): - if ip == attach["ip_address"]: - serial = ser + extension_values = {} + extension_values["VRF_LITE_CONN"] = [] + ms_con = {} + ms_con["MULTISITE_CONN"] = [] + extension_values["MULTISITE_CONN"] = json.dumps(ms_con) - if not serial: - msg = f"Fabric {self.fabric} does not contain switch " - msg += f"{attach['ip_address']}" - self.module.fail_json(msg=msg) + # Before applying the vrf_lite config, verify that the + # switch role begins with border role = self.inventory_data[attach["ip_address"]].get("switchRole") - if role.lower() in ("spine", "super spine"): - msg = f"VRFs cannot be attached to switch {attach['ip_address']} " + if not re.search(r"\bborder\b", role.lower()): + msg = f"{self.class_name}.{method_name}: " + msg += f"VRF LITE cannot be attached to switch {attach['ip_address']} " msg += f"with role {role}" self.module.fail_json(msg=msg) - ext_values = {} - ext_values["VRF_LITE_CONN"] = [] - ms_con = {} - ms_con["MULTISITE_CONN"] = [] - ext_values["MULTISITE_CONN"] = json.dumps(ms_con) - - if attach["vrf_lite"]: - # Before applying the vrf_lite config, verify that the switch role - # begins with border - if not re.search(r"\bborder\b", role.lower()): - msg = f"VRF LITE cannot be attached to switch {attach['ip_address']} " - msg += f"with role {role}" - self.module.fail_json(msg=msg) + for item in attach["vrf_lite"]: + + # If the playbook contains vrf lite parameters + # update the extension values. + vrf_lite_conn = {} + for param in self.vrf_lite_properties: + vrf_lite_conn[param] = "" + + if item["interface"]: + vrf_lite_conn["IF_NAME"] = item["interface"] + if item["dot1q"]: + vrf_lite_conn["DOT1Q_ID"] = str(item["dot1q"]) + if item["ipv4_addr"]: + vrf_lite_conn["IP_MASK"] = item["ipv4_addr"] + if item["neighbor_ipv4"]: + vrf_lite_conn["NEIGHBOR_IP"] = item["neighbor_ipv4"] + if item["ipv6_addr"]: + vrf_lite_conn["IPV6_MASK"] = item["ipv6_addr"] + if item["neighbor_ipv6"]: + vrf_lite_conn["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] + if item["peer_vrf"]: + vrf_lite_conn["PEER_VRF_NAME"] = item["peer_vrf"] + + vrf_lite_conn["VRF_LITE_JYTHON_TEMPLATE"] = "Ext_VRF_Lite_Jython" + + msg = ( + f"vrf_lite_conn: {json.dumps(vrf_lite_conn, indent=4, sort_keys=True)}" + ) + self.log.debug(msg) - for item in attach["vrf_lite"]: - - # If the playbook contains vrf lite elements add the - # extension values - nbr_dict = {} - for param in self.vrf_lite_properties: - nbr_dict[param] = "" - - if item["interface"]: - nbr_dict["IF_NAME"] = item["interface"] - if item["dot1q"]: - nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) - if item["ipv4_addr"]: - nbr_dict["IP_MASK"] = item["ipv4_addr"] - if item["neighbor_ipv4"]: - nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] - if item["ipv6_addr"]: - nbr_dict["IPV6_MASK"] = item["ipv6_addr"] - if item["neighbor_ipv6"]: - nbr_dict["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] - if item["peer_vrf"]: - nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] - - process_attachment = False - for value in nbr_dict.values(): - if value != "": - process_attachment = True - if not process_attachment: - msg = f"{self.class_name}.{method_name}: " - msg += "No attachment to process. Skipping." - self.log.debug(msg) - continue + vrf_lite_connections = {} + vrf_lite_connections["VRF_LITE_CONN"] = [] + vrf_lite_connections["VRF_LITE_CONN"].append(copy.deepcopy(vrf_lite_conn)) - nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = "Ext_VRF_Lite_Jython" + if extension_values["VRF_LITE_CONN"]: + extension_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend( + vrf_lite_connections["VRF_LITE_CONN"] + ) + else: + extension_values["VRF_LITE_CONN"] = copy.deepcopy(vrf_lite_connections) - msg = f"{self.class_name}.{method_name}: " - msg += f"nbr_dict: {json.dumps(nbr_dict, indent=4, sort_keys=True)}" - self.log.debug(msg) + extension_values["VRF_LITE_CONN"] = json.dumps( + extension_values["VRF_LITE_CONN"] + ) - vrflite_con = {} - vrflite_con["VRF_LITE_CONN"] = [] - vrflite_con["VRF_LITE_CONN"].append(copy.deepcopy(nbr_dict)) + msg = "Returning extension_values: " + msg += f"{json.dumps(extension_values, indent=4, sort_keys=True)}" + self.log.debug(msg) - if ext_values["VRF_LITE_CONN"]: - ext_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend( - vrflite_con["VRF_LITE_CONN"] - ) - else: - ext_values["VRF_LITE_CONN"] = vrflite_con + return copy.deepcopy(extension_values) + + def update_attach_params(self, attach, vrf_name, deploy, vlan_id) -> dict: + """ + # Summary - ext_values["VRF_LITE_CONN"] = json.dumps(ext_values["VRF_LITE_CONN"]) + Turn an attachment object (attach) into a payload for the controller. - msg = f"{self.class_name}.{method_name}: " - msg += "ext_values: " - msg += f"{json.dumps(ext_values, indent=4, sort_keys=True)}" + ## Raises + + Calls fail_json() if: + + - The switch in the attachment object is a spine + - If the vrf_lite object is not null, and the switch is not + a border switch + """ + self.log.debug("ENTERED") + + if not attach: + msg = "Early return. No attachments to process." self.log.debug(msg) + return {} + + # Not sure why this was needed? + # attach["ip_address"] = dcnm_get_ip_addr_info( + # self.module, attach["ip_address"], None, None + # ) + + serial = self.ip_to_serial_number(attach["ip_address"]) + + msg = f"ip_address: {attach['ip_address']}, " + msg += f"serial: {serial}, " + msg += f"attach: {json.dumps(attach, indent=4, sort_keys=True)}" + self.log.debug(msg) + + if not serial: + msg = f"Fabric {self.fabric} does not contain switch " + msg += f"{attach['ip_address']}" + self.module.fail_json(msg=msg) + + role = self.inventory_data[attach["ip_address"]].get("switchRole") - vrf_ext = True + if role.lower() in ("spine", "super spine"): + msg = f"VRFs cannot be attached to switch {attach['ip_address']} " + msg += f"with role {role}" + self.module.fail_json(msg=msg) + + extension_values = self.update_attach_params_extension_values(attach) + if extension_values: + attach.update( + {"extensionValues": json.dumps(extension_values).replace(" ", "")} + ) + else: + attach.update({"extensionValues": ""}) attach.update({"fabric": self.fabric}) attach.update({"vrfName": vrf_name}) @@ -1033,14 +1158,6 @@ def update_attach_params(self, attach, vrf_name, deploy, vlan_id): attach.update({"serialNumber": serial}) attach.update({"is_deploy": deploy}) - msg = f"{self.class_name}.{method_name}: " - msg += f"vrf_ext: {vrf_ext}" - self.log.debug(msg) - - if vrf_ext: - attach.update({"extensionValues": json.dumps(ext_values).replace(" ", "")}) - else: - attach.update({"extensionValues": ""}) # freeformConfig, loopbackId, loopbackIpAddress, and loopbackIpV6Address # will be copied from have attach.update({"freeformConfig": ""}) @@ -1057,16 +1174,17 @@ def update_attach_params(self, attach, vrf_name, deploy, vlan_id): } ) attach.update({"instanceValues": json.dumps(inst_values).replace(" ", "")}) + if "deploy" in attach: del attach["deploy"] if "ip_address" in attach: del attach["ip_address"] - msg = f"{self.class_name}.{method_name}: " - msg += "attach: " + msg = "attach: " msg += f"{json.dumps(attach, indent=4, sort_keys=True)}" self.log.debug(msg) - return attach + + return copy.deepcopy(attach) def dict_values_differ(self, dict1, dict2, skip_keys=None) -> bool: """ @@ -1077,9 +1195,7 @@ def dict_values_differ(self, dict1, dict2, skip_keys=None) -> bool: - Return True if the values for any (non-skipped) keys differs. - Return False otherwise """ - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") if skip_keys is None: skip_keys = [] @@ -1090,8 +1206,7 @@ def dict_values_differ(self, dict1, dict2, skip_keys=None) -> bool: dict1_value = str(dict1[key]).lower() dict2_value = str(dict2[key]).lower() if dict1_value != dict2_value: - msg = f"{self.class_name}.{method_name}: " - msg += f"Values differ: key {key} " + msg = f"Values differ: key {key} " msg += f"dict1_value {dict1_value} != dict2_value {dict2_value}. " msg += "returning True" self.log.debug(msg) @@ -1099,9 +1214,7 @@ def dict_values_differ(self, dict1, dict2, skip_keys=None) -> bool: return False def diff_for_create(self, want, have): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") conf_changed = False if not have: @@ -1124,8 +1237,8 @@ def diff_for_create(self, want, have): json_to_dict_want, json_to_dict_have, skip_keys=skip_keys ) - msg = f"{self.class_name}.{method_name}: " - msg += f"templates_differ: {templates_differ}, vlan_id_want: {vlan_id_want}" + msg = f"templates_differ: {templates_differ}, " + msg += f"vlan_id_want: {vlan_id_want}" self.log.debug(msg) if want["vrfId"] is not None and have["vrfId"] != want["vrfId"]: @@ -1145,17 +1258,14 @@ def diff_for_create(self, want, have): else: pass - msg = f"{self.class_name}.{method_name}: " - msg += f"returning conf_changed: {conf_changed}, " + msg = f"returning conf_changed: {conf_changed}, " msg += f"create: {create}" self.log.debug(msg) return create, conf_changed def update_create_params(self, vrf, vlanId=""): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") if not vrf: return vrf @@ -1251,21 +1361,20 @@ def get_vrf_lite_objects(self, attach) -> dict: - serialNumber: The serial_number of the switch - vrfName: The vrf to search """ - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") + verb = "GET" path = self.paths["GET_VRF_SWITCH"].format( attach["fabric"], attach["vrfName"], attach["serialNumber"] ) - lite_objects = dcnm_send(self.module, "GET", path) + msg = f"verb: {verb}, path: {path}" + self.log.debug(msg) + lite_objects = dcnm_send(self.module, verb, path) return copy.deepcopy(lite_objects) def get_have(self): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") have_create = [] have_deploy = {} @@ -1408,8 +1517,10 @@ def get_have(self): # with the attach['extensionvalues'] lite_objects = self.get_vrf_lite_objects(attach) - msg = f"{self.class_name}.{method_name}: " + if not lite_objects.get("DATA"): + msg = "Early return. lite_objects missing DATA" + self.log.debug(msg) return for sdl in lite_objects["DATA"]: @@ -1466,39 +1577,46 @@ def get_have(self): self.have_attach = have_attach self.have_deploy = have_deploy - msg = f"{self.class_name}.{method_name}: " - msg += f"self.have_create: {json.dumps(self.have_create, indent=4)}" + msg = "self.have_create: " + msg += f"{json.dumps(self.have_create, indent=4)}" self.log.debug(msg) # json.dumps() here breaks unit tests since self.have_attach is # a MagicMock and not JSON serializable. - msg = f"{self.class_name}.{method_name}: " - msg += f"self.have_attach: {self.have_attach}" + msg = "self.have_attach: " + msg += f"{self.have_attach}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += f"self.have_deploy: {json.dumps(self.have_deploy, indent=4)}" + msg = "self.have_deploy: " + msg += f"{json.dumps(self.have_deploy, indent=4)}" self.log.debug(msg) def get_want(self): method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") want_create = [] want_attach = [] want_deploy = {} - all_vrfs = "" + msg = "self.config " + msg += f"{json.dumps(self.config, indent=4)}" + self.log.debug(msg) - if not self.config: - return + all_vrfs = [] - msg = f"{self.class_name}.{method_name}: " - msg += f"self.config {json.dumps(self.config, indent=4)}" + msg = "self.validated: " + msg += f"{json.dumps(self.validated, indent=4, sort_keys=True)}" self.log.debug(msg) for vrf in self.validated: + vrf_name = vrf.get("vrf_name") + if not vrf_name: + msg = f"{self.class_name}.{method_name}: " + msg += f"vrf missing mandatory key vrf_name: {vrf}" + self.module.fail_json(msg=msg) + + all_vrfs.append(vrf_name) vrf_attach = {} vrfs = [] @@ -1511,46 +1629,42 @@ def get_want(self): want_create.append(self.update_create_params(vrf, vlan_id)) if not vrf.get("attach"): + msg = f"No attachments for vrf {vrf_name}. Skipping." + self.log.debug(msg) continue for attach in vrf["attach"]: deploy = vrf_deploy vrfs.append( - self.update_attach_params(attach, vrf["vrf_name"], deploy, vlan_id) + self.update_attach_params(attach, vrf_name, deploy, vlan_id) ) if vrfs: - vrf_attach.update({"vrfName": vrf["vrf_name"]}) + vrf_attach.update({"vrfName": vrf_name}) vrf_attach.update({"lanAttachList": vrfs}) want_attach.append(vrf_attach) - all_vrfs += vrf["vrf_name"] + "," - if all_vrfs: - want_deploy.update({"vrfNames": all_vrfs[:-1]}) + vrf_names = ",".join(all_vrfs) + want_deploy.update({"vrfNames": vrf_names}) - self.want_create = want_create - self.want_attach = want_attach - self.want_deploy = want_deploy + self.want_create = copy.deepcopy(want_create) + self.want_attach = copy.deepcopy(want_attach) + self.want_deploy = copy.deepcopy(want_deploy) - msg = f"{self.class_name}.{method_name}: " - msg += "self.want_create: " + msg = "self.want_create: " msg += f"{json.dumps(self.want_create, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.want_attach: " + msg = "self.want_attach: " msg += f"{json.dumps(self.want_attach, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.want_deploy: " + msg = "self.want_deploy: " msg += f"{json.dumps(self.want_deploy, indent=4)}" self.log.debug(msg) def get_diff_delete(self): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") @staticmethod def get_items_to_detach(attach_list): @@ -1612,35 +1726,27 @@ def get_items_to_detach(attach_list): self.diff_undeploy = diff_undeploy self.diff_delete = diff_delete - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_detach: " + msg = "self.diff_detach: " msg += f"{json.dumps(self.diff_detach, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_undeploy: " + msg = "self.diff_undeploy: " msg += f"{json.dumps(self.diff_undeploy, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_delete: " + msg = "self.diff_delete: " msg += f"{json.dumps(self.diff_delete, indent=4)}" self.log.debug(msg) def get_diff_override(self): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") all_vrfs = "" diff_delete = {} self.get_diff_replace() - diff_create = self.diff_create - diff_attach = self.diff_attach diff_detach = self.diff_detach - diff_deploy = self.diff_deploy diff_undeploy = self.diff_undeploy for have_a in self.have_attach: @@ -1667,52 +1773,28 @@ def get_diff_override(self): if all_vrfs: diff_undeploy.update({"vrfNames": all_vrfs[:-1]}) - self.diff_create = diff_create - self.diff_attach = diff_attach - self.diff_deploy = diff_deploy + self.diff_delete = diff_delete self.diff_detach = diff_detach self.diff_undeploy = diff_undeploy - self.diff_delete = diff_delete - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_create: " - msg += f"{json.dumps(self.diff_create, indent=4)}" - self.log.debug(msg) - - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_attach: " - msg += f"{json.dumps(self.diff_attach, indent=4)}" - self.log.debug(msg) - - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_deploy: " - msg += f"{json.dumps(self.diff_deploy, indent=4)}" + msg = "self.diff_delete: " + msg += f"{json.dumps(self.diff_delete, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_detach: " + msg = "self.diff_detach: " msg += f"{json.dumps(self.diff_detach, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_undeploy: " + msg = "self.diff_undeploy: " msg += f"{json.dumps(self.diff_undeploy, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_delete: " - msg += f"{json.dumps(self.diff_delete, indent=4)}" - self.log.debug(msg) - def get_diff_replace(self): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") all_vrfs = "" self.get_diff_merge(replace=True) - diff_create = self.diff_create diff_attach = self.diff_attach diff_deploy = self.diff_deploy @@ -1774,7 +1856,6 @@ def get_diff_replace(self): all_vrfs += have_a["vrfName"] + "," if not all_vrfs: - self.diff_create = diff_create self.diff_attach = diff_attach self.diff_deploy = diff_deploy return @@ -1785,22 +1866,14 @@ def get_diff_replace(self): vrfs = self.diff_deploy["vrfNames"] + "," + all_vrfs[:-1] diff_deploy.update({"vrfNames": vrfs}) - self.diff_create = diff_create - self.diff_attach = diff_attach - self.diff_deploy = diff_deploy - - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_create: " - msg += f"{json.dumps(self.diff_create, indent=4)}" - self.log.debug(msg) + self.diff_attach = copy.deepcopy(diff_attach) + self.diff_deploy = copy.deepcopy(diff_deploy) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_attach: " + msg = "self.diff_attach: " msg += f"{json.dumps(self.diff_attach, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_deploy: " + msg = "self.diff_deploy: " msg += f"{json.dumps(self.diff_deploy, indent=4)}" self.log.debug(msg) @@ -1817,8 +1890,7 @@ def get_next_vrf_id(self, fabric) -> int: - Unable to retrieve next available vrf_id for fabric """ method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") attempt = 0 vrf_id = None @@ -1861,9 +1933,7 @@ def get_next_vrf_id(self, fabric) -> int: return vrf_id def diff_merge_create(self, replace=False): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED with " - msg += f"replace == {replace}" + msg = f"ENTERED with replace == {replace}" self.log.debug(msg) conf_changed = {} @@ -1877,28 +1947,25 @@ def diff_merge_create(self, replace=False): for have_c in self.have_create: if want_c["vrfName"] == have_c["vrfName"]: vrf_found = True - msg = f"{self.class_name}.{method_name}: " - msg += "Calling diff_for_create with: " + msg = "Calling diff_for_create with: " msg += f"want_c: {json.dumps(want_c, indent=4, sort_keys=True)}, " msg += f"have_c: {json.dumps(have_c, indent=4, sort_keys=True)}" self.log.debug(msg) diff, conf_chg = self.diff_for_create(want_c, have_c) - msg = f"{self.class_name}.{method_name}: " - msg += "diff_for_create() returned with: " + msg = "diff_for_create() returned with: " msg += f"conf_chg {conf_chg}, " msg += f"diff {json.dumps(diff, indent=4, sort_keys=True)}, " self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += f"Updating conf_changed[{want_c['vrfName']}] with {conf_chg}" + msg = f"Updating conf_changed[{want_c['vrfName']}] " + msg += f"with {conf_chg}" self.log.debug(msg) conf_changed.update({want_c["vrfName"]: conf_chg}) if diff: - msg = f"{self.class_name}.{method_name}: " - msg += "Appending diff_create_update with " + msg = "Appending diff_create_update with " msg += f"{json.dumps(diff, indent=4, sort_keys=True)}" self.log.debug(msg) diff_create_update.append(diff) @@ -2008,25 +2075,20 @@ def diff_merge_create(self, replace=False): self.diff_create_update = diff_create_update self.diff_create_quick = diff_create_quick - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_create: " + msg = "self.diff_create: " msg += f"{json.dumps(self.diff_create, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_create_quick: " + msg = "self.diff_create_quick: " msg += f"{json.dumps(self.diff_create_quick, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_create_update: " + msg = "self.diff_create_update: " msg += f"{json.dumps(self.diff_create_update, indent=4)}" self.log.debug(msg) def diff_merge_attach(self, replace=False): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED with " - msg += f"replace == {replace}" + msg = f"ENTERED with replace == {replace}" self.log.debug(msg) diff_attach = [] @@ -2057,8 +2119,7 @@ def diff_merge_attach(self, replace=False): ): deploy_vrf = want_a["vrfName"] - msg = f"{self.class_name}.{method_name}: " - msg += f"attach_found: {attach_found}" + msg = f"attach_found: {attach_found}" self.log.debug(msg) if not attach_found and want_a.get("lanAttachList"): @@ -2072,7 +2133,7 @@ def diff_merge_attach(self, replace=False): del base["lanAttachList"] base.update({"lanAttachList": attach_list}) diff_attach.append(base) - if attach["is_deploy"]: + if attach.get("is_deploy") is True: deploy_vrf = want_a["vrfName"] for atch in attach_list: @@ -2087,20 +2148,16 @@ def diff_merge_attach(self, replace=False): self.diff_attach = diff_attach self.diff_deploy = diff_deploy - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_attach: " + msg = "self.diff_attach: " msg += f"{json.dumps(self.diff_attach, indent=4)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_deploy: " + msg = "self.diff_deploy: " msg += f"{json.dumps(self.diff_deploy, indent=4)}" self.log.debug(msg) def get_diff_merge(self, replace=False): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED with " - msg += f"replace == {replace}" + msg = f"ENTERED with replace == {replace}" self.log.debug(msg) # Special cases: @@ -2114,9 +2171,7 @@ def get_diff_merge(self, replace=False): self.diff_merge_attach(replace) def format_diff(self): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") diff = [] @@ -2132,38 +2187,31 @@ def format_diff(self): self.diff_undeploy["vrfNames"].split(",") if self.diff_undeploy else [] ) - msg = f"{self.class_name}.{method_name}: INPUT: " - msg += "diff_create: " + msg = "INPUT: diff_create: " msg += f"{json.dumps(diff_create, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: INPUT: " - msg += "diff_create_quick: " + msg = "INPUT: diff_create_quick: " msg += f"{json.dumps(diff_create_quick, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: INPUT: " - msg += "diff_create_update: " + msg = "INPUT: diff_create_update: " msg += f"{json.dumps(diff_create_update, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: INPUT: " - msg += "diff_attach: " + msg = "INPUT: diff_attach: " msg += f"{json.dumps(diff_attach, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: INPUT: " - msg += "diff_detach: " + msg = "INPUT: diff_detach: " msg += f"{json.dumps(diff_detach, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: INPUT: " - msg += "diff_deploy: " + msg = "INPUT: diff_deploy: " msg += f"{json.dumps(diff_deploy, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: INPUT: " - msg += "diff_undeploy: " + msg = "INPUT: diff_undeploy: " msg += f"{json.dumps(diff_undeploy, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -2174,8 +2222,7 @@ def format_diff(self): for want_d in diff_create: - msg = f"{self.class_name}.{method_name}: " - msg += "want_d: " + msg = "want_d: " msg += f"{json.dumps(want_d, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -2183,15 +2230,13 @@ def format_diff(self): search=diff_attach, key="vrfName", value=want_d["vrfName"] ) - msg = f"{self.class_name}.{method_name}: " - msg += "found_a: " + msg = "found_a: " msg += f"{json.dumps(found_a, indent=4, sort_keys=True)}" self.log.debug(msg) found_c = copy.deepcopy(want_d) - msg = f"{self.class_name}.{method_name}: " - msg += "found_c: PRE_UPDATE: " + msg = "found_c: PRE_UPDATE: " msg += f"{json.dumps(found_c, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -2289,16 +2334,14 @@ def format_diff(self): del found_c["serviceVrfTemplate"] del found_c["vrfTemplateConfig"] - msg = f"{self.class_name}.{method_name}: " - msg += "found_c: POST_UPDATE: " + msg = "found_c: POST_UPDATE: " msg += f"{json.dumps(found_c, indent=4, sort_keys=True)}" self.log.debug(msg) if diff_deploy and found_c["vrf_name"] in diff_deploy: diff_deploy.remove(found_c["vrf_name"]) if not found_a: - msg = f"{self.class_name}.{method_name}: " - msg += "not found_a. Appending found_c to diff." + msg = "not found_a. Appending found_c to diff." self.log.debug(msg) diff.append(found_c) continue @@ -2316,8 +2359,7 @@ def format_diff(self): attach_d.update({"deploy": a_w["deployment"]}) found_c["attach"].append(attach_d) - msg = f"{self.class_name}.{method_name}: " - msg += "Appending found_c to diff." + msg = "Appending found_c to diff." self.log.debug(msg) diff.append(found_c) @@ -2352,15 +2394,13 @@ def format_diff(self): self.diff_input_format = copy.deepcopy(diff) - msg = f"{self.class_name}.{method_name}: " - msg += "self.diff_input_format: " + msg = "self.diff_input_format: " msg += f"{json.dumps(self.diff_input_format, indent=4, sort_keys=True)}" self.log.debug(msg) def get_diff_query(self): method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") path = self.paths["GET_VRF"].format(self.fabric) vrf_objects = dcnm_send(self.module, "GET", path) @@ -2493,23 +2533,16 @@ def push_diff_create_update(self, is_rollback=False): Send diff_create_update to the controller """ - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") + action = "create" path = self.paths["GET_VRF"].format(self.fabric) + verb = "PUT" if self.diff_create_update: for vrf in self.diff_create_update: update_path = f"{path}/{vrf['vrfName']}" - resp = dcnm_send(self.module, "PUT", update_path, json.dumps(vrf)) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "create") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) + self.send_to_controller(action, verb, update_path, vrf, is_rollback) def push_diff_detach(self, is_rollback=False): """ @@ -2517,16 +2550,11 @@ def push_diff_detach(self, is_rollback=False): Send diff_detach to the controller """ - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") if not self.diff_detach: return - path = self.paths["GET_VRF"].format(self.fabric) - detach_path = path + "/attachments" - # For multiste fabric, update the fabric name to the child fabric # containing the switches if self.fabric_type == "MFD": @@ -2539,14 +2567,13 @@ def push_diff_detach(self, is_rollback=False): if "is_deploy" in vrf_attach.keys(): del vrf_attach["is_deploy"] - resp = dcnm_send(self.module, "POST", detach_path, json.dumps(self.diff_detach)) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "attach") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) + action = "attach" + path = self.paths["GET_VRF"].format(self.fabric) + detach_path = path + "/attachments" + verb = "POST" + self.send_to_controller( + action, verb, detach_path, self.diff_detach, is_rollback + ) def push_diff_undeploy(self, is_rollback=False): """ @@ -2554,26 +2581,19 @@ def push_diff_undeploy(self, is_rollback=False): Send diff_undeploy to the controller """ - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") if not self.diff_undeploy: return + action = "deploy" path = self.paths["GET_VRF"].format(self.fabric) deploy_path = path + "/deployments" + verb = "POST" - resp = dcnm_send( - self.module, "POST", deploy_path, json.dumps(self.diff_undeploy) + self.send_to_controller( + action, verb, deploy_path, self.diff_undeploy, is_rollback ) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "deploy") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) def push_diff_delete(self, is_rollback=False): """ @@ -2582,13 +2602,14 @@ def push_diff_delete(self, is_rollback=False): Send diff_delete to the controller """ method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") if not self.diff_delete: return + action = "delete" path = self.paths["GET_VRF"].format(self.fabric) + verb = "DELETE" del_failure = "" @@ -2597,15 +2618,10 @@ def push_diff_delete(self, is_rollback=False): if state == "OUT-OF-SYNC": del_failure += vrf + "," continue - delete_path = path + "/" + vrf - resp = dcnm_send(self.module, "DELETE", delete_path) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "delete") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) + delete_path = f"{path}/{vrf}" + self.send_to_controller( + action, verb, delete_path, self.diff_detach, is_rollback + ) if del_failure: msg = f"{self.class_name}.{method_name}: " @@ -2620,14 +2636,11 @@ def push_diff_create(self, is_rollback=False): Send diff_create to the controller """ method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") if not self.diff_create: return - path = self.paths["GET_VRF"].format(self.fabric) - for vrf in self.diff_create: json_to_dict = json.loads(vrf["vrfTemplateConfig"]) vlan_id = json_to_dict.get("vrfVlanId", "0") @@ -2696,14 +2709,206 @@ def push_diff_create(self, is_rollback=False): vrf.update({"vrfTemplateConfig": json.dumps(t_conf)}) - resp = dcnm_send(self.module, "POST", path, json.dumps(vrf)) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "create") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) + action = "create" + verb = "POST" + path = self.paths["GET_VRF"].format(self.fabric) + + self.send_to_controller(action, verb, path, vrf, is_rollback) + + def is_border_switch(self, serial_number) -> bool: + """ + # Summary + + Given a switch serial_number: + + - Return True if the switch is a border switch + - Return False otherwise + """ + is_border = False + for ip, serial in self.ip_sn.items(): + if serial != serial_number: + continue + role = self.inventory_data[ip].get("switchRole") + r = re.search(r"\bborder\b", role.lower()) + if r: + is_border = True + return is_border + + def get_extension_values_from_lite_object(self, lite): + """ + Given a lite object, return: + + - The lite object's extensionValues, if any. + - None, if the lite object has no extensionValues + """ + extension_values = None + for item in lite: + if str(item.get("extensionType")) != "VRF_LITE": + continue + extension_values = item["extensionValues"] + extension_values = ast.literal_eval(extension_values) + return extension_values + + def add_vrf_lite_extension_to_vrf_attach(self, vrf_attach, lite): + method_name = inspect.stack()[0][3] + self.log.debug("ENTERED") + + serial_number = vrf_attach.get("serialNumber") + + msg = f"serial_number: {serial_number}, " + msg += "Received vrf_attach: " + msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"serial_number: {serial_number}, " + msg += "Received lite: " + msg += f"{json.dumps(lite, indent=4, sort_keys=True)}" + self.log.debug(msg) + + ext_values = None + extension_values = {} + extension_values["VRF_LITE_CONN"] = [] + extension_values["MULTISITE_CONN"] = [] + + ext_values = self.get_extension_values_from_lite_object(lite) + if ext_values is None: + ip_address = self.serial_number_to_ip(serial_number) + msg = f"{self.class_name}.{method_name}: " + msg += "No VRF LITE capable interfaces found on " + msg += "this switch. " + msg += f"ip: {ip_address}, " + msg += f"serial_number: {serial_number}" + self.module.fail_json(msg=msg) + + for item in vrf_attach.get("vrf_lite"): + if item["interface"] != ext_values["IF_NAME"]: + continue + nbr_dict = {} + nbr_dict["IF_NAME"] = item["interface"] + + if item["dot1q"]: + nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) + else: + nbr_dict["DOT1Q_ID"] = str(ext_values["DOT1Q_ID"]) + + if item["ipv4_addr"]: + nbr_dict["IP_MASK"] = item["ipv4_addr"] + else: + nbr_dict["IP_MASK"] = ext_values["IP_MASK"] + + if item["neighbor_ipv4"]: + nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] + else: + nbr_dict["NEIGHBOR_IP"] = ext_values["NEIGHBOR_IP"] + + nbr_dict["NEIGHBOR_ASN"] = ext_values["NEIGHBOR_ASN"] + + if item["ipv6_addr"]: + nbr_dict["IPV6_MASK"] = item["ipv6_addr"] + else: + nbr_dict["IPV6_MASK"] = ext_values["IPV6_MASK"] + + if item["neighbor_ipv6"]: + nbr_dict["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] + else: + nbr_dict["IPV6_NEIGHBOR"] = ext_values["IPV6_NEIGHBOR"] + + nbr_dict["AUTO_VRF_LITE_FLAG"] = ext_values["AUTO_VRF_LITE_FLAG"] + + if item["peer_vrf"]: + nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] + else: + nbr_dict["PEER_VRF_NAME"] = ext_values["PEER_VRF_NAME"] + + nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = "Ext_VRF_Lite_Jython" + vrflite_con = {} + vrflite_con["VRF_LITE_CONN"] = [] + vrflite_con["VRF_LITE_CONN"].append(copy.deepcopy(nbr_dict)) + if extension_values["VRF_LITE_CONN"]: + extension_values["VRF_LITE_CONN"]["VRF_LITE_CONN"].extend( + vrflite_con["VRF_LITE_CONN"] + ) + else: + extension_values["VRF_LITE_CONN"] = vrflite_con + + ms_con = {} + ms_con["MULTISITE_CONN"] = [] + extension_values["MULTISITE_CONN"] = json.dumps(ms_con) + + extension_values["VRF_LITE_CONN"] = json.dumps( + extension_values["VRF_LITE_CONN"] + ) + vrf_attach["extensionValues"] = json.dumps(extension_values).replace(" ", "") + if vrf_attach.get("vrf_lite") is not None: + del vrf_attach["vrf_lite"] + + msg = "Returning modified vrf_attach: " + msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) + return copy.deepcopy(vrf_attach) + + def ip_to_serial_number(self, ip_address): + """ + Given a switch ip_address, return the switch serial number. + + If ip_address is not found, return None. + """ + return self.ip_sn.get(ip_address) + + def serial_number_to_ip(self, serial_number): + """ + Given a switch serial_number, return the switch ip address. + + If serial_number is not found, return None. + """ + return self.sn_ip.get(serial_number) + + def send_to_controller(self, action, verb, path, payload, is_rollback=False): + caller = inspect.stack()[1][3] + msg = "ENTERED. " + msg += f"caller: {caller}" + self.log.debug(msg) + + msg = "TX controller: " + msg += f"caller: {caller}, " + msg += f"verb: {verb}, " + msg += f"path: {path}, " + msg += "payload: " + msg += f"{json.dumps(payload, indent=4, sort_keys=True)}" + self.log.debug(msg) + + if payload is not None: + resp = dcnm_send(self.module, verb, path, json.dumps(payload)) + else: + resp = dcnm_send(self.module, verb, path) + + msg = "RX controller: " + msg += f"caller: {caller}, " + msg += f"verb: {verb}, " + msg += f"path: {path}, " + msg += "response: " + msg += f"{json.dumps(resp, indent=4, sort_keys=True)}" + self.log.debug(msg) + + msg = f"caller: {caller}, " + msg += "Calling self.handle_response." + self.log.debug(msg) + + self.result["response"].append(resp) + fail, self.result["changed"] = self.handle_response(resp, action) + + msg = f"caller: {caller}, " + msg += "Calling self.handle_response. DONE" + self.log.debug(msg) + + if fail: + if is_rollback: + self.failed_to_rollback = True + return + msg = f"caller: {caller}, " + msg += "Calling self.failure." + self.log.debug(msg) + self.failure(resp) def push_diff_attach(self, is_rollback=False): """ @@ -2712,178 +2917,120 @@ def push_diff_attach(self, is_rollback=False): Send diff_attach to the controller """ method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" + msg = "NEW_CODE ENTERED" + self.log.debug(msg) + + msg = "self.diff_attach PRE: " + msg += f"{json.dumps(self.diff_attach, indent=4, sort_keys=True)}" self.log.debug(msg) if not self.diff_attach: - msg = f"{self.class_name}.{method_name}: " - msg += "Early return. self.diff_attach is empty. " + msg = "Early return. self.diff_attach is empty. " msg += f"{json.dumps(self.diff_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) return + new_diff_attach_list = [] for diff_attach in self.diff_attach: - msg = f"{self.class_name}.{method_name}: " - msg += "diff_attach: " + msg = "diff_attach: " msg += f"{json.dumps(diff_attach, indent=4, sort_keys=True)}" self.log.debug(msg) + new_lan_attach_list = [] for vrf_attach in diff_attach["lanAttachList"]: - msg = f"{self.class_name}.{method_name}: " + vrf_attach.update(vlan=0) + + serial_number = vrf_attach.get("serialNumber") + msg = f"serial_number: {serial_number}, " msg += "vrf_attach: " msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" self.log.debug(msg) - vrf_attach.update(vlan=0) if "is_deploy" in vrf_attach.keys(): del vrf_attach["is_deploy"] - if vrf_attach.get("vrf_lite"): - for ip, ser in self.ip_sn.items(): - if ser == vrf_attach["serialNumber"]: - # Before applying the vrf_lite config, verify that - # the switch role beings with "border" - role = self.inventory_data[ip].get("switchRole") - r = re.search(r"\bborder\b", role.lower()) - if not r: - msg = f"{self.class_name}.{method_name}: " - msg += "VRF LITE cannot be attached to " - msg += f"switch {ip} with role {role}" - self.module.fail_json(msg=msg) - - lite_objects = self.get_vrf_lite_objects(vrf_attach) - - msg = f"{self.class_name}.{method_name}: " - msg += "lite_objects: " - msg += f"{json.dumps(lite_objects, indent=4, sort_keys=True)}" + if not vrf_attach.get("vrf_lite"): + new_lan_attach_list.append(vrf_attach) + msg = f"serial_number: {serial_number}, " + msg += "Skipping vrf_lite: " + msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" self.log.debug(msg) + if "vrf_lite" in vrf_attach: + del vrf_attach["vrf_lite"] + continue - if not lite_objects.get("DATA"): - msg = f"{self.class_name}.{method_name}: " - msg += "Early return, no lite objects." - return + msg = f"serial_number: {serial_number}, " + msg += "vrf_attach.get(vrf_lite): " + msg += f"{json.dumps(vrf_attach.get('vrf_lite'), indent=4, sort_keys=True)}" + self.log.debug(msg) - lite = lite_objects["DATA"][0]["switchDetailsList"][0][ - "extensionPrototypeValues" - ] + if not self.is_border_switch(serial_number): + # arobel TODO: Not covered by UT + ip_address = self.serial_number_to_ip(serial_number) msg = f"{self.class_name}.{method_name}: " - msg += "lite: " - msg += f"{json.dumps(lite, indent=4, sort_keys=True)}" + msg += "VRF LITE cannot be attached to " + msg += "non-border switch. " + msg += f"ip: {ip_address}, " + msg += f"serial number: {serial_number}" + self.module.fail_json(msg=msg) + + lite_objects = self.get_vrf_lite_objects(vrf_attach) + + msg = f"serial_number: {serial_number}, " + msg += "lite_objects: " + msg += f"{json.dumps(lite_objects, indent=4, sort_keys=True)}" + self.log.debug(msg) + + if not lite_objects.get("DATA"): + msg = f"serial_number: {serial_number}, " + msg += "Early return, no lite objects." self.log.debug(msg) + return - ext_values = None - extension_values = {} - extension_values["VRF_LITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = [] + lite = lite_objects["DATA"][0]["switchDetailsList"][0][ + "extensionPrototypeValues" + ] + msg = f"serial_number: {serial_number}, " + msg += "lite: " + msg += f"{json.dumps(lite, indent=4, sort_keys=True)}" + self.log.debug(msg) - for ext_l in lite: - if str(ext_l.get("extensionType")) != "VRF_LITE": - continue - ext_values = ext_l["extensionValues"] - ext_values = ast.literal_eval(ext_values) - for item in vrf_attach.get("vrf_lite"): - if item["interface"] == ext_values["IF_NAME"]: - nbr_dict = {} - nbr_dict["IF_NAME"] = item["interface"] - - if item["dot1q"]: - nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) - else: - nbr_dict["DOT1Q_ID"] = str(ext_values["DOT1Q_ID"]) - - if item["ipv4_addr"]: - nbr_dict["IP_MASK"] = item["ipv4_addr"] - else: - nbr_dict["IP_MASK"] = ext_values["IP_MASK"] - - if item["neighbor_ipv4"]: - nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] - else: - nbr_dict["NEIGHBOR_IP"] = ext_values["NEIGHBOR_IP"] - - nbr_dict["NEIGHBOR_ASN"] = ext_values["NEIGHBOR_ASN"] - - if item["ipv6_addr"]: - nbr_dict["IPV6_MASK"] = item["ipv6_addr"] - else: - nbr_dict["IPV6_MASK"] = ext_values["IPV6_MASK"] - - if item["neighbor_ipv6"]: - nbr_dict["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] - else: - nbr_dict["IPV6_NEIGHBOR"] = ext_values[ - "IPV6_NEIGHBOR" - ] - - nbr_dict["AUTO_VRF_LITE_FLAG"] = ext_values[ - "AUTO_VRF_LITE_FLAG" - ] - - if item["peer_vrf"]: - nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] - else: - nbr_dict["PEER_VRF_NAME"] = ext_values[ - "PEER_VRF_NAME" - ] - - nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = ( - "Ext_VRF_Lite_Jython" - ) - vrflite_con = {} - vrflite_con["VRF_LITE_CONN"] = [] - vrflite_con["VRF_LITE_CONN"].append( - copy.deepcopy(nbr_dict) - ) - if extension_values["VRF_LITE_CONN"]: - extension_values["VRF_LITE_CONN"][ - "VRF_LITE_CONN" - ].extend(vrflite_con["VRF_LITE_CONN"]) - else: - extension_values["VRF_LITE_CONN"] = vrflite_con - - ms_con = {} - ms_con["MULTISITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = json.dumps(ms_con) - - del item - - if ext_values is None: - for ip, ser in self.ip_sn.items(): - # arobel TODO: Not covered by UT - if ser == vrf_attach["serialNumber"]: - msg = f"{self.class_name}.{method_name}: " - msg += "No VRF LITE capable interfaces found " - msg += f"on this switch {ip}" - self.module.fail_json(msg=msg) - else: - extension_values["VRF_LITE_CONN"] = json.dumps( - extension_values["VRF_LITE_CONN"] - ) - vrf_attach["extensionValues"] = json.dumps( - extension_values - ).replace(" ", "") - if vrf_attach.get("vrf_lite", None) is not None: - del vrf_attach["vrf_lite"] + msg = "old vrf_attach: " + msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) - else: - if "vrf_lite" in vrf_attach.keys(): - del vrf_attach["vrf_lite"] + vrf_attach = self.add_vrf_lite_extension_to_vrf_attach(vrf_attach, lite) + msg = "new vrf_attach: " + msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) + new_lan_attach_list.append(vrf_attach) + + msg = "Updating diff_attach[lanAttachList] with: " + msg += f"{json.dumps(new_lan_attach_list, indent=4, sort_keys=True)}" + self.log.debug(msg) + + diff_attach["lanAttachList"] = copy.deepcopy(new_lan_attach_list) + new_diff_attach_list.append(copy.deepcopy(diff_attach)) + + msg = "new_diff_attach_list: " + msg += f"{json.dumps(new_diff_attach_list, indent=4, sort_keys=True)}" + self.log.debug(msg) + + action = "attach" + verb = "POST" path = self.paths["GET_VRF"].format(self.fabric) attach_path = path + "/attachments" # For multisite fabrics, update the fabric name to the child fabric # containing the switches. if self.fabric_type == "MFD": - for elem in self.diff_attach: + for elem in new_diff_attach_list: for node in elem["lanAttachList"]: node["fabric"] = self.sn_fab[node["serialNumber"]] - resp = dcnm_send(self.module, "POST", attach_path, json.dumps(self.diff_attach)) - self.result["response"].append(resp) - fail, self.result["changed"] = self.handle_response(resp, "attach") - if fail: - if is_rollback: - self.failed_to_rollback = True - return - self.failure(resp) + + self.send_to_controller( + action, verb, attach_path, new_diff_attach_list, is_rollback + ) def push_diff_deploy(self, is_rollback=False): """ @@ -2891,16 +3038,105 @@ def push_diff_deploy(self, is_rollback=False): Send diff_deploy to the controller """ - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") if not self.diff_deploy: return + action = "deploy" + verb = "POST" path = self.paths["GET_VRF"].format(self.fabric) deploy_path = path + "/deployments" - resp = dcnm_send(self.module, "POST", deploy_path, json.dumps(self.diff_deploy)) + + self.send_to_controller( + action, verb, deploy_path, self.diff_deploy, is_rollback + ) + + def release_resources_by_id(self, id_list=None): + method_name = inspect.stack()[0][3] + self.log.debug("ENTERED") + + if id_list is None: + return + if not isinstance(id_list, list): + msg = f"{self.class_name}.{method_name}: " + msg += "id_list must be a list of resource IDs. " + msg += f"Got: {id_list}." + self.module.fail_json(msg) + + try: + id_list = [int(x) for x in id_list] + except ValueError as error: + msg = f"{self.class_name}.{method_name}: " + msg += "id_list must be a list of resource IDs. " + msg += "Where each id is convertable to integer." + msg += f"Got: {id_list}. " + msg += f"Error detail: {error}" + self.module.fail_json(msg) + + id_list = [str(x) for x in id_list] + + msg = f"Releasing resource IDs: {','.join(id_list)}" + self.log.debug(msg) + + action = "deploy" + path = "/appcenter/cisco/ndfc/api/v1/lan-fabric" + path += "/rest/resource-manager/resources" + path += f"?id={','.join(id_list)}" + verb = "DELETE" + self.send_to_controller(action, verb, path, None) + + def release_orphaned_resources(self, vrf, is_rollback=False): + """ + # Summary + + Release orphaned resources. + + ## Description + + After a VRF delete operation, resources such as the TOP_DOWN_VRF_VLAN + resource below, can be orphaned from their VRFs. Below, notice that + resourcePool.vrfName is null. This method releases resources if + the following are true for the resources: + + - allocatedFlag is False + - entityName == vrf + - fabricName == self.fabric + + ```json + [ + { + "id": 36368, + "resourcePool": { + "id": 0, + "poolName": "TOP_DOWN_VRF_VLAN", + "fabricName": "f1", + "vrfName": null, + "poolType": "ID_POOL", + "dynamicSubnetRange": null, + "targetSubnet": 0, + "overlapAllowed": false, + "hierarchicalKey": "f1" + }, + "entityType": "Device", + "entityName": "VRF_1", + "allocatedIp": "201", + "allocatedOn": 1734040978066, + "allocatedFlag": false, + "allocatedScopeValue": "FDO211218GC", + "ipAddress": "172.22.150.103", + "switchName": "cvd-1312-leaf", + "hierarchicalKey": "0" + } + ] + ``` + """ + self.log.debug("ENTERED") + + path = "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/" + path += f"resource-manager/fabric/{self.fabric}/" + path += "pools/TOP_DOWN_VRF_VLAN" + resp = dcnm_send(self.module, "GET", path) self.result["response"].append(resp) fail, self.result["changed"] = self.handle_response(resp, "deploy") if fail: @@ -2909,15 +3145,33 @@ def push_diff_deploy(self, is_rollback=False): return self.failure(resp) + delete_ids = [] + for item in resp["DATA"]: + if "entityName" not in item: + continue + if item["entityName"] != vrf: + continue + if item.get("allocatedFlag") is not False: + continue + if item.get("id") is None: + continue + + msg = f"item {json.dumps(item, indent=4, sort_keys=True)}" + self.log.debug(msg) + + delete_ids.append(item["id"]) + + if len(delete_ids) == 0: + return + self.release_resources_by_id(delete_ids) + def push_to_remote(self, is_rollback=False): """ # Summary Send all diffs to the controller """ - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") self.push_diff_create_update(is_rollback) @@ -2930,6 +3184,9 @@ def push_to_remote(self, is_rollback=False): self.push_diff_undeploy(is_rollback) self.push_diff_delete(is_rollback) + for vrf in self.diff_delete.keys(): + self.release_orphaned_resources(vrf, is_rollback) + self.push_diff_create(is_rollback) self.push_diff_attach(is_rollback) self.push_diff_deploy(is_rollback) @@ -2945,8 +3202,7 @@ def wait_for_vrf_del_ready(self): Calls fail_json if VRF has associated network attachments. """ method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") for vrf in self.diff_delete: ok_to_delete = False @@ -2960,8 +3216,7 @@ def wait_for_vrf_del_ready(self): continue attach_list = resp["DATA"][0]["lanAttachList"] - msg = f"{self.class_name}.{method_name}: " - msg += f"ok_to_delete: {ok_to_delete}, " + msg = f"ok_to_delete: {ok_to_delete}, " msg += f"attach_list: {json.dumps(attach_list, indent=4)}" self.log.debug(msg) @@ -3011,7 +3266,7 @@ def attach_spec(self): spec["import_evpn_rt"] = {"default": "", "type": "str"} spec["export_evpn_rt"] = {"default": "", "type": "str"} - if self.params["state"] in ("merged", "overridden", "replaced"): + if self.state in ("merged", "overridden", "replaced"): spec["vrf_lite"] = {"type": "list"} else: spec["vrf_lite"] = {"default": [], "type": "list"} @@ -3032,7 +3287,7 @@ def lite_spec(self): spec["neighbor_ipv6"] = {"type": "ipv6"} spec["peer_vrf"] = {"type": "str"} - if self.params["state"] in ("merged", "overridden", "replaced"): + if self.state in ("merged", "overridden", "replaced"): spec["interface"] = {"required": True, "type": "str"} else: spec["interface"] = {"type": "str"} @@ -3122,7 +3377,7 @@ def vrf_spec(self): } spec["vrf_vlan_name"] = {"default": "", "type": "str"} - if self.params["state"] in ("merged", "overridden", "replaced"): + if self.state in ("merged", "overridden", "replaced"): spec["deploy"] = {"default": True, "type": "bool"} else: spec["deploy"] = {"type": "bool"} @@ -3132,35 +3387,29 @@ def vrf_spec(self): def validate_input(self): """Parse the playbook values, validate to param specs.""" method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) - - state = self.params["state"] + self.log.debug("ENTERED") attach_spec = self.attach_spec() lite_spec = self.lite_spec() vrf_spec = self.vrf_spec() - msg = f"{self.class_name}.{method_name}: " - msg += "attach_spec: " + msg = "attach_spec: " msg += f"{json.dumps(attach_spec, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "lite_spec: " + msg = "lite_spec: " msg += f"{json.dumps(lite_spec, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = f"{self.class_name}.{method_name}: " - msg += "vrf_spec: " + msg = "vrf_spec: " msg += f"{json.dumps(vrf_spec, indent=4, sort_keys=True)}" self.log.debug(msg) - if state in ("merged", "overridden", "replaced"): + if self.state in ("merged", "overridden", "replaced"): fail_msg_list = [] if self.config: for vrf in self.config: - msg = f"{self.class_name}.{method_name}: state {state}: " + msg = f"state {self.state}: " msg += "self.config[vrf]: " msg += f"{json.dumps(vrf, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -3182,10 +3431,9 @@ def validate_input(self): "ip_address is mandatory under attach parameters" ) else: - if state in ("merged", "replaced"): - fail_msg_list.append( - f"config element is mandatory for {state} state" - ) + if self.state in ("merged", "replaced"): + msg = f"config element is mandatory for {self.state} state" + fail_msg_list.append(msg) if fail_msg_list: msg = f"{self.class_name}.{method_name}: " @@ -3198,7 +3446,7 @@ def validate_input(self): ) for vrf in valid_vrf: - msg = f"{self.class_name}.{method_name}: state {state}: " + msg = f"state {self.state}: " msg += "valid_vrf[vrf]: " msg += f"{json.dumps(vrf, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -3209,7 +3457,7 @@ def validate_input(self): valid_att, invalid_att = validate_list_of_dicts( vrf["attach"], attach_spec ) - msg = f"{self.class_name}.{method_name}: state {state}: " + msg = f"state {self.state}: " msg += "valid_att: " msg += f"{json.dumps(valid_att, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -3221,9 +3469,7 @@ def validate_input(self): valid_lite, invalid_lite = validate_list_of_dicts( lite["vrf_lite"], lite_spec ) - msg = ( - f"{self.class_name}.{method_name}: state {state}: " - ) + msg = f"state {self.state}: " msg += "valid_lite: " msg += f"{json.dumps(valid_lite, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -3269,9 +3515,7 @@ def validate_input(self): self.module.fail_json(msg=msg) def handle_response(self, res, op): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: ENTERED" - self.log.debug(msg) + self.log.debug("ENTERED") fail = False changed = True diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml index 374c8ee39..0f20cff62 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml @@ -81,7 +81,7 @@ ### MERGED ## ############################################### -- name: TEST.1 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 +- name: TEST.1 - MERGED - [merged] Create, Attach, Deploy VLAN(600)+VRF ansible-vrf-int1 cisco.dcnm.dcnm_vrf: &conf1 fabric: "{{ fabric_1 }}" state: merged @@ -90,7 +90,7 @@ vrf_id: 9008011 vrf_template: Default_VRF_Universal vrf_extension_template: Default_VRF_Extension_Universal - vlan_id: 500 + vlan_id: 600 attach: - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" @@ -152,7 +152,7 @@ timeout: 60 when: result_1e.changed == true -- name: TEST.2 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF (controller provided) +- name: TEST.2 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF (controller provided VLAN) cisco.dcnm.dcnm_vrf: &conf2 fabric: "{{ fabric_1 }}" state: merged diff --git a/tests/unit/modules/dcnm/fixtures/dcnm_vrf.json b/tests/unit/modules/dcnm/fixtures/dcnm_vrf.json index 7d562b0eb..08b8e2671 100644 --- a/tests/unit/modules/dcnm/fixtures/dcnm_vrf.json +++ b/tests/unit/modules/dcnm/fixtures/dcnm_vrf.json @@ -1101,6 +1101,36 @@ } ] }, + "mock_pools_top_down_vrf_vlan": { + "ERROR": "", + "RETURN_CODE": 200, + "MESSAGE":"OK", + "DATA": [ + { + "allocatedFlag": true, + "allocatedIp": "201", + "allocatedOn": 1734051260507, + "allocatedScopeValue": "FDO211218HH", + "entityName": "VRF_1", + "entityType": "Device", + "hierarchicalKey": "0", + "id": 36407, + "ipAddress": "172.22.150.104", + "resourcePool": { + "dynamicSubnetRange": null, + "fabricName": "f1", + "hierarchicalKey": "f1", + "id": 0, + "overlapAllowed": false, + "poolName": "TOP_DOWN_VRF_VLAN", + "poolType": "ID_POOL", + "targetSubnet": 0, + "vrfName": "VRF_1" + }, + "switchName": "cvd-1313-leaf" + } + ] + }, "mock_vrf_lite_obj": { "RETURN_CODE":200, "METHOD":"GET", diff --git a/tests/unit/modules/dcnm/test_dcnm_vrf.py b/tests/unit/modules/dcnm/test_dcnm_vrf.py index b505c5fb6..372b090ee 100644 --- a/tests/unit/modules/dcnm/test_dcnm_vrf.py +++ b/tests/unit/modules/dcnm/test_dcnm_vrf.py @@ -127,6 +127,7 @@ def init_data(self): self.test_data.get("mock_vrf_attach_lite_object") ) self.mock_vrf_lite_obj = copy.deepcopy(self.test_data.get("mock_vrf_lite_obj")) + self.mock_pools_top_down_vrf_vlan = copy.deepcopy(self.test_data.get("mock_pools_top_down_vrf_vlan")) def setUp(self): super(TestDcnmVrfModule, self).setUp() @@ -437,6 +438,7 @@ def load_fixtures(self, response=None, device=""): self.mock_vrf_attach_object_del_not_ready, self.mock_vrf_attach_object_del_ready, self.delete_success_resp, + self.mock_pools_top_down_vrf_vlan, self.blank_data, self.attach_success_resp2, self.deploy_success_resp, @@ -472,6 +474,7 @@ def load_fixtures(self, response=None, device=""): self.mock_vrf_attach_object_del_not_ready, self.mock_vrf_attach_object_del_ready, self.delete_success_resp, + self.mock_pools_top_down_vrf_vlan, ] elif "delete_std_lite" in self._testMethodName: @@ -519,6 +522,7 @@ def load_fixtures(self, response=None, device=""): obj1, obj2, self.delete_success_resp, + self.mock_pools_top_down_vrf_vlan, ] elif "query" in self._testMethodName: @@ -719,10 +723,10 @@ def test_dcnm_vrf_merged_lite_invalidrole(self): ) ) result = self.execute_module(changed=False, failed=True) - self.assertEqual( - result["msg"], - "VRF LITE cannot be attached to switch 10.10.10.225 with role leaf", - ) + msg = "DcnmVrf.update_attach_params_extension_values: " + msg += "VRF LITE cannot be attached to switch 10.10.10.225 " + msg += "with role leaf" + self.assertEqual(result["msg"], msg) def test_dcnm_vrf_merged_with_update(self): set_module_args( @@ -1025,10 +1029,10 @@ def test_dcnm_vrf_override_with_deletions(self): self.assertEqual(result["response"][1]["DATA"]["status"], "") self.assertEqual(result["response"][1]["RETURN_CODE"], self.SUCCESS_RETURN_CODE) self.assertEqual( - result["response"][4]["DATA"]["test-vrf-2--XYZKSJHSMK2(leaf2)"], "SUCCESS" + result["response"][5]["DATA"]["test-vrf-2--XYZKSJHSMK2(leaf2)"], "SUCCESS" ) self.assertEqual( - result["response"][4]["DATA"]["test-vrf-2--XYZKSJHSMK3(leaf3)"], "SUCCESS" + result["response"][5]["DATA"]["test-vrf-2--XYZKSJHSMK3(leaf3)"], "SUCCESS" ) def test_dcnm_vrf_lite_override_with_deletions(self): From c30cfd41642dfae671e6145d0cbd004064bec5a9 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 16 Dec 2024 06:50:18 -1000 Subject: [PATCH 47/49] dcnm_vrf: Fix for issue #357 1. Potential fix for issue #357 If any interface in the playbook task's vrf_lite configuration does not match an interface on the switch that had extensionValues, call fail_json(). - Refactor vrf_lite processing out of push_diff_attach() and into: - update_vrf_attach_vrf_lite_extensions() - In update_vrf_attach_vrf_lite_extensions() verify that all interfaces in the playbook's vrf_lite section match an interface on the switch that has extensionValues. If this check fails, call fail_json() 2. Rename get_extension_values_from_lite_object() to get_extension_values_from_lite_objects() and be explicit that the method takes a list of objects and returns a list of objects, or an empty list. 3. Add some debug statements 4. Rename vrf to vrf_name in push_to_remote() --- plugins/modules/dcnm_vrf.py | 228 +++++++++++++++++++++++++----------- 1 file changed, 161 insertions(+), 67 deletions(-) diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index b7158fdd9..f6a637c5a 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -717,6 +717,7 @@ def find_dict_in_list_by_key_value(search: list, key: str, value: str): match = (d for d in search if d[key] == value) return next(match, None) + # pylint: disable=inconsistent-return-statements def to_bool(self, key, dict_with_key): """ # Summary @@ -745,6 +746,7 @@ def to_bool(self, key, dict_with_key): msg += "is not convertable to boolean" self.module.fail_json(msg=msg) + # pylint: enable=inconsistent-return-statements @staticmethod def compare_properties(dict1, dict2, property_list): """ @@ -1026,9 +1028,9 @@ def update_attach_params_extension_values(self, attach) -> dict: self.log.debug(msg) return {} - extension_values = {} + extension_values: dict = {} extension_values["VRF_LITE_CONN"] = [] - ms_con = {} + ms_con: dict = {} ms_con["MULTISITE_CONN"] = [] extension_values["MULTISITE_CONN"] = json.dumps(ms_con) @@ -1073,7 +1075,7 @@ def update_attach_params_extension_values(self, attach) -> dict: ) self.log.debug(msg) - vrf_lite_connections = {} + vrf_lite_connections: dict = {} vrf_lite_connections["VRF_LITE_CONN"] = [] vrf_lite_connections["VRF_LITE_CONN"].append(copy.deepcopy(vrf_lite_conn)) @@ -1264,7 +1266,7 @@ def diff_for_create(self, want, have): return create, conf_changed - def update_create_params(self, vrf, vlanId=""): + def update_create_params(self, vrf, vlan_id=""): self.log.debug("ENTERED") if not vrf: @@ -1291,7 +1293,7 @@ def update_create_params(self, vrf, vlanId=""): template_conf = { "vrfSegmentId": vrf.get("vrf_id", None), "vrfName": vrf["vrf_name"], - "vrfVlanId": vlanId, + "vrfVlanId": vlan_id, "vrfVlanName": vrf.get("vrf_vlan_name", ""), "vrfIntfDescription": vrf.get("vrf_intf_desc", ""), "vrfDescription": vrf.get("vrf_description", ""), @@ -1930,7 +1932,7 @@ def get_next_vrf_id(self, fabric) -> int: msg += "Unable to retrieve vrf_id " msg += f"for fabric {fabric}" self.module.fail_json(msg) - return vrf_id + return int(str(vrf_id)) def diff_merge_create(self, replace=False): msg = f"ENTERED with replace == {replace}" @@ -2127,17 +2129,17 @@ def diff_merge_attach(self, replace=False): for attach in want_a["lanAttachList"]: if attach.get("isAttached"): del attach["isAttached"] + if attach.get("is_deploy") is True: + deploy_vrf = want_a["vrfName"] + attach["deployment"] = True attach_list.append(copy.deepcopy(attach)) if attach_list: base = want_a.copy() del base["lanAttachList"] base.update({"lanAttachList": attach_list}) diff_attach.append(base) - if attach.get("is_deploy") is True: - deploy_vrf = want_a["vrfName"] - - for atch in attach_list: - atch["deployment"] = True + # for atch in attach_list: + # atch["deployment"] = True if deploy_vrf: all_vrfs += deploy_vrf + "," @@ -2734,22 +2736,54 @@ def is_border_switch(self, serial_number) -> bool: is_border = True return is_border - def get_extension_values_from_lite_object(self, lite): + def get_extension_values_from_lite_objects(self, lite: list[dict]) -> list: """ - Given a lite object, return: + # Summary + + Given a list of lite objects, return: + + - A list containing the extensionValues, if any, from these + lite objects. + - An empty list, if the lite objects have no extensionValues - - The lite object's extensionValues, if any. - - None, if the lite object has no extensionValues + ## Raises + + None """ - extension_values = None + extension_values_list: list[dict] = [] for item in lite: if str(item.get("extensionType")) != "VRF_LITE": continue extension_values = item["extensionValues"] extension_values = ast.literal_eval(extension_values) - return extension_values + extension_values_list.append(extension_values) + return extension_values_list + + def update_vrf_attach_vrf_lite_extensions(self, vrf_attach, lite) -> dict: + """ + # Summary + + ## params + - vrf_attach + A vrf_attach object containing a vrf_lite extension + to update + - lite: A list of current vrf_lite extension objects from + the switch - def add_vrf_lite_extension_to_vrf_attach(self, vrf_attach, lite): + ## Description + + Merge the values from the vrf_attach object into a matching vrf_lite + extension object (if any) from the switch. Update the vrf_attach + object with the merged result. Return the updated vrf_attach + object. If no matching vrf_lite extension object is found on the + switch, return the unmodified vrf_attach object. + + "matching" in this case means: + + 1. The extensionType of the switch's extension object is VRF_LITE + 2. The IF_NAME in the extensionValues of the extension object + matches the interface in vrf_attach.vrf_lite. + """ method_name = inspect.stack()[0][3] self.log.debug("ENTERED") @@ -2760,17 +2794,22 @@ def add_vrf_lite_extension_to_vrf_attach(self, vrf_attach, lite): msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" self.log.debug(msg) + if vrf_attach.get("vrf_lite") is None: + if "vrf_lite" in vrf_attach: + del vrf_attach["vrf_lite"] + vrf_attach["extensionValues"] = "" + msg = "vrf_attach does not contain a vrf_lite configuration. " + msg += "Returning it with empty extensionValues. " + msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" + self.log.debug(msg) + return copy.deepcopy(vrf_attach) + msg = f"serial_number: {serial_number}, " msg += "Received lite: " msg += f"{json.dumps(lite, indent=4, sort_keys=True)}" self.log.debug(msg) - ext_values = None - extension_values = {} - extension_values["VRF_LITE_CONN"] = [] - extension_values["MULTISITE_CONN"] = [] - - ext_values = self.get_extension_values_from_lite_object(lite) + ext_values = self.get_extension_values_from_lite_objects(lite) if ext_values is None: ip_address = self.serial_number_to_ip(serial_number) msg = f"{self.class_name}.{method_name}: " @@ -2780,48 +2819,99 @@ def add_vrf_lite_extension_to_vrf_attach(self, vrf_attach, lite): msg += f"serial_number: {serial_number}" self.module.fail_json(msg=msg) + matches: dict = {} + # user_vrf_lite_interfaces and switch_vrf_lite_interfaces + # are used in fail_json message when no matches interfaces + # are found on the switch + user_vrf_lite_interfaces = [] + switch_vrf_lite_interfaces = [] for item in vrf_attach.get("vrf_lite"): - if item["interface"] != ext_values["IF_NAME"]: - continue + item_interface = item.get("interface") + user_vrf_lite_interfaces.append(item_interface) + for ext_value in ext_values: + ext_value_interface = ext_value.get("IF_NAME") + switch_vrf_lite_interfaces.append(ext_value_interface) + msg = f"item_interface: {item_interface}, " + msg += f"ext_value_interface: {ext_value_interface}" + self.log.debug(msg) + if item_interface == ext_value_interface: + msg = "Found item: " + msg += f"item[interface] {item_interface}, == " + msg += f"ext_values[IF_NAME] {ext_value_interface}, " + msg += f"{json.dumps(item)}" + self.log.debug(msg) + matches[item_interface] = {} + matches[item_interface]["user"] = item + matches[item_interface]["switch"] = ext_value + if not matches: + # No matches. fail_json here to avoid the following 500 error + # "Provided interface doesn't have extensions" + ip_address = self.serial_number_to_ip(serial_number) + msg = "No matching interfaces with vrf_lite extensions " + msg += f"found on switch {ip_address} ({serial_number}). " + msg += "playbook vrf_lite_interfaces: " + msg += f"{','.join(sorted(user_vrf_lite_interfaces))}. " + msg += "switch vrf_lite_interfaces: " + msg += f"{','.join(sorted(switch_vrf_lite_interfaces))}." + self.log.debug(msg) + self.module.fail_json(msg) + + msg = "Matching extension object(s) found on the switch." + msg += "Proceeding to convert playbook vrf_lite configuration " + msg += "to payload format." + self.log.debug(msg) + + extension_values: dict = {} + extension_values["VRF_LITE_CONN"] = [] + extension_values["MULTISITE_CONN"] = [] + + msg = f"matches: {json.dumps(matches, indent=4, sort_keys=True)}" + self.log.debug(msg) + + for interface, item in matches.items(): + msg = f"interface: {interface}: " + msg += f"{json.dumps(item, indent=4, sort_keys=True)}" + self.log.debug(msg) + nbr_dict = {} - nbr_dict["IF_NAME"] = item["interface"] + nbr_dict["IF_NAME"] = item["user"]["interface"] - if item["dot1q"]: - nbr_dict["DOT1Q_ID"] = str(item["dot1q"]) + if item["user"]["dot1q"]: + nbr_dict["DOT1Q_ID"] = str(item["user"]["dot1q"]) else: - nbr_dict["DOT1Q_ID"] = str(ext_values["DOT1Q_ID"]) + nbr_dict["DOT1Q_ID"] = str(item["switch"]["DOT1Q_ID"]) - if item["ipv4_addr"]: - nbr_dict["IP_MASK"] = item["ipv4_addr"] + if item["user"]["ipv4_addr"]: + nbr_dict["IP_MASK"] = item["user"]["ipv4_addr"] else: - nbr_dict["IP_MASK"] = ext_values["IP_MASK"] + nbr_dict["IP_MASK"] = item["switch"]["IP_MASK"] - if item["neighbor_ipv4"]: - nbr_dict["NEIGHBOR_IP"] = item["neighbor_ipv4"] + if item["user"]["neighbor_ipv4"]: + nbr_dict["NEIGHBOR_IP"] = item["user"]["neighbor_ipv4"] else: - nbr_dict["NEIGHBOR_IP"] = ext_values["NEIGHBOR_IP"] + nbr_dict["NEIGHBOR_IP"] = item["switch"]["NEIGHBOR_IP"] - nbr_dict["NEIGHBOR_ASN"] = ext_values["NEIGHBOR_ASN"] + nbr_dict["NEIGHBOR_ASN"] = item["switch"]["NEIGHBOR_ASN"] - if item["ipv6_addr"]: - nbr_dict["IPV6_MASK"] = item["ipv6_addr"] + if item["user"]["ipv6_addr"]: + nbr_dict["IPV6_MASK"] = item["user"]["ipv6_addr"] else: - nbr_dict["IPV6_MASK"] = ext_values["IPV6_MASK"] + nbr_dict["IPV6_MASK"] = item["switch"]["IPV6_MASK"] - if item["neighbor_ipv6"]: - nbr_dict["IPV6_NEIGHBOR"] = item["neighbor_ipv6"] + if item["user"]["neighbor_ipv6"]: + nbr_dict["IPV6_NEIGHBOR"] = item["user"]["neighbor_ipv6"] else: - nbr_dict["IPV6_NEIGHBOR"] = ext_values["IPV6_NEIGHBOR"] + nbr_dict["IPV6_NEIGHBOR"] = item["switch"]["IPV6_NEIGHBOR"] - nbr_dict["AUTO_VRF_LITE_FLAG"] = ext_values["AUTO_VRF_LITE_FLAG"] + nbr_dict["AUTO_VRF_LITE_FLAG"] = item["switch"]["AUTO_VRF_LITE_FLAG"] - if item["peer_vrf"]: - nbr_dict["PEER_VRF_NAME"] = item["peer_vrf"] + if item["user"]["peer_vrf"]: + nbr_dict["PEER_VRF_NAME"] = item["user"]["peer_vrf"] else: - nbr_dict["PEER_VRF_NAME"] = ext_values["PEER_VRF_NAME"] + nbr_dict["PEER_VRF_NAME"] = item["switch"]["PEER_VRF_NAME"] nbr_dict["VRF_LITE_JYTHON_TEMPLATE"] = "Ext_VRF_Lite_Jython" - vrflite_con = {} + vrflite_con: dict = {} vrflite_con["VRF_LITE_CONN"] = [] vrflite_con["VRF_LITE_CONN"].append(copy.deepcopy(nbr_dict)) if extension_values["VRF_LITE_CONN"]: @@ -2831,13 +2921,13 @@ def add_vrf_lite_extension_to_vrf_attach(self, vrf_attach, lite): else: extension_values["VRF_LITE_CONN"] = vrflite_con - ms_con = {} + ms_con: dict = {} ms_con["MULTISITE_CONN"] = [] extension_values["MULTISITE_CONN"] = json.dumps(ms_con) - extension_values["VRF_LITE_CONN"] = json.dumps( - extension_values["VRF_LITE_CONN"] - ) + extension_values["VRF_LITE_CONN"] = json.dumps( + extension_values["VRF_LITE_CONN"] + ) vrf_attach["extensionValues"] = json.dumps(extension_values).replace(" ", "") if vrf_attach.get("vrf_lite") is not None: del vrf_attach["vrf_lite"] @@ -2941,31 +3031,31 @@ def push_diff_attach(self, is_rollback=False): vrf_attach.update(vlan=0) serial_number = vrf_attach.get("serialNumber") - msg = f"serial_number: {serial_number}, " + ip_address = self.serial_number_to_ip(serial_number) + msg = f"ip_address {ip_address} ({serial_number}), " msg += "vrf_attach: " msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" self.log.debug(msg) - if "is_deploy" in vrf_attach.keys(): + if "is_deploy" in vrf_attach: del vrf_attach["is_deploy"] if not vrf_attach.get("vrf_lite"): + if "vrf_lite" in vrf_attach: + del vrf_attach["vrf_lite"] new_lan_attach_list.append(vrf_attach) - msg = f"serial_number: {serial_number}, " + msg = f"ip_address {ip_address} ({serial_number}), " msg += "Skipping vrf_lite: " msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" self.log.debug(msg) - if "vrf_lite" in vrf_attach: - del vrf_attach["vrf_lite"] continue - msg = f"serial_number: {serial_number}, " + msg = f"ip_address {ip_address} ({serial_number}), " msg += "vrf_attach.get(vrf_lite): " msg += f"{json.dumps(vrf_attach.get('vrf_lite'), indent=4, sort_keys=True)}" self.log.debug(msg) if not self.is_border_switch(serial_number): # arobel TODO: Not covered by UT - ip_address = self.serial_number_to_ip(serial_number) msg = f"{self.class_name}.{method_name}: " msg += "VRF LITE cannot be attached to " msg += "non-border switch. " @@ -2975,13 +3065,13 @@ def push_diff_attach(self, is_rollback=False): lite_objects = self.get_vrf_lite_objects(vrf_attach) - msg = f"serial_number: {serial_number}, " + msg = f"ip_address {ip_address} ({serial_number}), " msg += "lite_objects: " msg += f"{json.dumps(lite_objects, indent=4, sort_keys=True)}" self.log.debug(msg) if not lite_objects.get("DATA"): - msg = f"serial_number: {serial_number}, " + msg = f"ip_address {ip_address} ({serial_number}), " msg += "Early return, no lite objects." self.log.debug(msg) return @@ -2989,17 +3079,21 @@ def push_diff_attach(self, is_rollback=False): lite = lite_objects["DATA"][0]["switchDetailsList"][0][ "extensionPrototypeValues" ] - msg = f"serial_number: {serial_number}, " + msg = f"ip_address {ip_address} ({serial_number}), " msg += "lite: " msg += f"{json.dumps(lite, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = "old vrf_attach: " + msg = f"ip_address {ip_address} ({serial_number}), " + msg += "old vrf_attach: " msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" self.log.debug(msg) - vrf_attach = self.add_vrf_lite_extension_to_vrf_attach(vrf_attach, lite) - msg = "new vrf_attach: " + vrf_attach = self.update_vrf_attach_vrf_lite_extensions( + vrf_attach, lite + ) + msg = f"ip_address {ip_address} ({serial_number}), " + msg += "new vrf_attach: " msg += f"{json.dumps(vrf_attach, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -3184,8 +3278,8 @@ def push_to_remote(self, is_rollback=False): self.push_diff_undeploy(is_rollback) self.push_diff_delete(is_rollback) - for vrf in self.diff_delete.keys(): - self.release_orphaned_resources(vrf, is_rollback) + for vrf_name in self.diff_delete: + self.release_orphaned_resources(vrf_name, is_rollback) self.push_diff_create(is_rollback) self.push_diff_attach(is_rollback) From d754e3e9c96222afdc84f2dbfb7001b4319e34eb Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 16 Dec 2024 07:43:31 -1000 Subject: [PATCH 48/49] dcnm_vrf IT: Update task titles, print results 1. Update task titles to group tests. 2. Print results before each assert stanza. 3. Increase pause after VRF deletion from 40 to 60 seconds. --- .../targets/dcnm_vrf/tests/dcnm/deleted.yaml | 120 +++++--- .../targets/dcnm_vrf/tests/dcnm/merged.yaml | 4 +- .../dcnm_vrf/tests/dcnm/overridden.yaml | 286 ++++++++++-------- .../targets/dcnm_vrf/tests/dcnm/query.yaml | 8 +- .../targets/dcnm_vrf/tests/dcnm/replaced.yaml | 14 +- 5 files changed, 254 insertions(+), 178 deletions(-) diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml index 94e19b807..c793b0a1d 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/deleted.yaml @@ -89,6 +89,10 @@ retries: 30 delay: 2 +- name: SETUP.4a - DELETED - [debug] print result_setup_3 + debug: + var: result_setup_3 + - assert: that: - 'result_setup_3.changed == true' @@ -118,6 +122,14 @@ vrf_extension_template: Default_VRF_Extension_Universal register: result_1 +- name: TEST.1a - DELETED - [wait_for] Wait 60 seconds for controller and switch to sync + wait_for: + timeout: 60 + +- name: TEST.1b - DELETED - [debug] print result_1 + debug: + var: result_1 + - assert: that: - 'result_1.changed == true' @@ -132,21 +144,21 @@ - 'result_1.diff[0].attach[1].deploy == false' - 'result_1.diff[0].vrf_name == "ansible-vrf-int1"' -- name: TEST.1a - DELETED - [wait_for] Wait 40 seconds for controller and switch to sync - wait_for: - timeout: 40 - -- name: TEST.1b - DELETED - [deleted] conf1 - Idempotence +- name: TEST.1c - DELETED - [deleted] conf1 - Idempotence cisco.dcnm.dcnm_vrf: *conf1 - register: result_1b + register: result_1c + +- name: TEST.1d - DELETED - [debug] print result_1c + debug: + var: result_1c - assert: that: - - 'result_1b.changed == false' - - 'result_1b.response|length == 0' - - 'result_1b.diff|length == 0' + - 'result_1c.changed == false' + - 'result_1c.response|length == 0' + - 'result_1c.diff|length == 0' -- name: TEST.2 - DELETED - [merged] Create, Attach, Deploy VLAN+VRF+LITE EXTENSION ansible-vrf-int1 switch_2 +- name: TEST.2 - DELETED - [merged] Create, Attach, Deploy VLAN+VRF+LITE ansible-vrf-int1 switch_2 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -166,7 +178,7 @@ neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 2 # dot1q can be got from dcnm + dot1q: 2 # optional controller can provide deploy: true register: result_2 @@ -180,6 +192,10 @@ retries: 30 delay: 2 +- name: TEST.2b - DELETED - [debug] print result_2 + debug: + var: result_2 + - assert: that: - 'result_2.changed == true' @@ -194,7 +210,7 @@ - '"{{ switch_2 }}" in result_2.diff[0].attach[1].ip_address' - 'result_2.diff[0].vrf_name == "ansible-vrf-int1"' -- name: TEST.2b - DELETED - [deleted] Delete VRF+LITE EXTENSION ansible-vrf-int1 switch_2 +- name: TEST.2b - DELETED - [deleted] Delete VRF+LITE ansible-vrf-int1 switch_2 cisco.dcnm.dcnm_vrf: &conf2 fabric: "{{ fabric_1 }}" state: deleted @@ -218,6 +234,10 @@ deploy: true register: result_2b +- name: TEST.2c - DELETED - [debug] print result_2b + debug: + var: result_2b + - assert: that: - 'result_2b.changed == true' @@ -232,24 +252,28 @@ - 'result_2b.diff[0].attach[1].deploy == false' - 'result_2b.diff[0].vrf_name == "ansible-vrf-int1"' -- name: TEST.2c - DELETED - [wait_for] Wait 40 seconds for controller and switch to sync +- name: TEST.2d - DELETED - [wait_for] Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: - timeout: 40 + timeout: 60 -- name: TEST.2d - DELETED - [deleted] conf2 - Idempotence +- name: TEST.2e - DELETED - [deleted] conf2 - Idempotence cisco.dcnm.dcnm_vrf: *conf2 - register: result_2d + register: result_2e + +- name: TEST.2f - DELETED - [debug] print result_2e + debug: + var: result_2e - assert: that: - - 'result_2d.changed == false' - - 'result_2d.response|length == 0' - - 'result_2d.diff|length == 0' + - 'result_2e.changed == false' + - 'result_2e.response|length == 0' + - 'result_2e.diff|length == 0' -- name: TEST.3 - DELETED - [merged] Create, Attach, Deploy VRF+LITE EXTENSION switch_2 +- name: TEST.3 - DELETED - [merged] Create, Attach, Deploy VRF+LITE switch_2 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -269,7 +293,7 @@ neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 2 # dot1q can be got from dcnm + dot1q: 2 # optional controller can provide deploy: true register: result_3 @@ -283,6 +307,10 @@ retries: 30 delay: 2 +- name: TEST.3b - DELETED - [debug] print result_3 + debug: + var: result_3 + - assert: that: - 'result_3.changed == true' @@ -297,43 +325,51 @@ - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' - 'result_3.diff[0].vrf_name == "ansible-vrf-int1"' -- name: TEST.3b - DELETED - [deleted] Delete VRF+LITE EXTENSION - empty config element +- name: TEST.3c - DELETED - [deleted] Delete VRF+LITE - empty config element cisco.dcnm.dcnm_vrf: &conf3 fabric: "{{ fabric_1 }}" state: deleted config: - register: result_3b + register: result_3c + +- name: TEST.3d - DELETED - [debug] print result_3c + debug: + var: result_3c - assert: that: - - 'result_3b.changed == true' - - 'result_3b.response[0].RETURN_CODE == 200' - - 'result_3b.response[1].RETURN_CODE == 200' - - 'result_3b.response[1].MESSAGE == "OK"' - - 'result_3b.response[2].RETURN_CODE == 200' - - 'result_3b.response[2].METHOD == "DELETE"' - - '(result_3b.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result_3b.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - 'result_3b.diff[0].attach[0].deploy == false' - - 'result_3b.diff[0].attach[1].deploy == false' - - 'result_3b.diff[0].vrf_name == "ansible-vrf-int1"' - -- name: TEST.3c - DELETED - [wait_for] Wait 40 seconds for controller and switch to sync + - 'result_3c.changed == true' + - 'result_3c.response[0].RETURN_CODE == 200' + - 'result_3c.response[1].RETURN_CODE == 200' + - 'result_3c.response[1].MESSAGE == "OK"' + - 'result_3c.response[2].RETURN_CODE == 200' + - 'result_3c.response[2].METHOD == "DELETE"' + - '(result_3c.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_3c.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_3c.diff[0].attach[0].deploy == false' + - 'result_3c.diff[0].attach[1].deploy == false' + - 'result_3c.diff[0].vrf_name == "ansible-vrf-int1"' + +- name: TEST.3d - DELETED - [wait_for] Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: - timeout: 40 + timeout: 60 -- name: TEST.3d - DELETED - conf3 - Idempotence +- name: TEST.3e - DELETED - conf3 - Idempotence cisco.dcnm.dcnm_vrf: *conf3 - register: result_3d + register: result_3e + +- name: TEST.3f - DELETED - [debug] print result_3e + debug: + var: result_3e - assert: that: - - 'result_3d.changed == false' - - 'result_3d.response|length == 0' - - 'result_3d.diff|length == 0' + - 'result_3e.changed == false' + - 'result_3e.response|length == 0' + - 'result_3e.diff|length == 0' ################################################ #### CLEAN-UP ## diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml index 0f20cff62..7bacbc501 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/merged.yaml @@ -219,7 +219,7 @@ wait_for: timeout: 60 -- name: TEST.3 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF+LITE EXTENSION ansible-vrf-int1 switch_2 (user provided) +- name: TEST.3 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF+LITE EXTENSION ansible-vrf-int1 switch_2 (user provided VLAN) cisco.dcnm.dcnm_vrf: &conf3 fabric: "{{ fabric_1 }}" state: merged @@ -296,7 +296,7 @@ wait_for: timeout: 60 -- name: TEST.4 - MERGED - [merged] Create, Attach, Deploy VRF+VRF LITE EXTENSION - one optional (controller provided) +- name: TEST.4 - MERGED - [merged] Create, Attach, Deploy VLAN+VRF+LITE EXTENSION - (controller provided VLAN) cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml index bddf59af9..c87515724 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml @@ -69,12 +69,12 @@ state: deleted register: result_setup_2 -- name: SETUP.2a - OVERRIDDEN - [wait_for] Wait 40 seconds for controller and switch to sync +- name: SETUP.2a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: - timeout: 40 + timeout: 60 when: result_setup_2.changed == true - name: SETUP.3 OVERRIDDEN - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 @@ -91,31 +91,35 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_setup_3 + +- name: SETUP.3a - OVERRIDDEN - [debug] print result_setup_3 + debug: + var: result_setup_3 - name: SETUP.4 OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_setup_4 until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_setup_4.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_setup_3.changed == true' + - 'result_setup_3.response[0].RETURN_CODE == 200' + - 'result_setup_3.response[1].RETURN_CODE == 200' + - 'result_setup_3.response[2].RETURN_CODE == 200' + - '(result_setup_3.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_setup_3.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_setup_3.diff[0].attach[0].deploy == true' + - 'result_setup_3.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_setup_3.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_setup_3.diff[0].attach[1].ip_address' + - 'result_setup_3.diff[0].vrf_name == "ansible-vrf-int1"' ############################################### ### OVERRIDDEN ## @@ -135,59 +139,69 @@ - ip_address: "{{ switch_1 }}" - ip_address: "{{ switch_2 }}" deploy: true - register: result + register: result_1 -- name: TEST.2 - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.1a - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_1a until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_1a.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.1b - OVERRIDDEN - [debug] print result_1 + debug: + var: result_1 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - 'result.response[3].RETURN_CODE == 200' - - 'result.response[4].RETURN_CODE == 200' - - 'result.response[5].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - '(result.response[4].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[4].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int2"' - - 'result.diff[1].attach[0].deploy == false' - - 'result.diff[1].attach[1].deploy == false' - - 'result.diff[1].vrf_name == "ansible-vrf-int1"' - -- name: TEST.3 - OVERRIDDEN - [overridden] conf1 - Idempotence + - 'result_1.changed == true' + - 'result_1.response[0].RETURN_CODE == 200' + - 'result_1.response[1].RETURN_CODE == 200' + - 'result_1.response[2].RETURN_CODE == 200' + - 'result_1.response[3].RETURN_CODE == 200' + - 'result_1.response[4].RETURN_CODE == 200' + - 'result_1.response[5].RETURN_CODE == 200' + - 'result_1.response[6].RETURN_CODE == 200' + - 'result_1.response[7].RETURN_CODE == 200' + - '(result_1.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_1.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - '(result_1.response[6].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_1.response[6].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_1.diff[0].attach[0].deploy == true' + - 'result_1.diff[0].attach[1].deploy == true' + - 'result_1.diff[0].vrf_name == "ansible-vrf-int2"' + - 'result_1.diff[1].attach[0].deploy == false' + - 'result_1.diff[1].attach[1].deploy == false' + - 'result_1.diff[1].vrf_name == "ansible-vrf-int1"' + +- name: TEST.1c - OVERRIDDEN - [overridden] conf1 - Idempotence cisco.dcnm.dcnm_vrf: *conf1 - register: result + register: result_1c + +- name: TEST.1d - OVERRIDDEN - [debug] print result_1c + debug: + var: result_1c - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_1c.changed == false' + - 'result_1c.response|length == 0' -- name: TEST.4 - OVERRIDDEN - [deleted] Delete all VRFs +- name: TEST.1f - OVERRIDDEN - [deleted] Delete all VRFs cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: deleted - register: result_4 + register: result_1f -- name: TEST.4a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync +- name: TEST.1g - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync wait_for: timeout: 60 - when: result_4.changed == true + when: result_1f.changed == true -- name: TEST.5 - OVERRIDDEN - [merged] ansible-vrf-int2 to add vrf_lite extension to switch_2 +- name: TEST.2 - OVERRIDDEN - [merged] ansible-vrf-int2 to add vrf_lite extension to switch_2 cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -207,44 +221,48 @@ neighbor_ipv4: 10.33.0.0 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 2 # dot1q can be got from dcnm + dot1q: 2 # optional controller can provide deploy: true - register: result + register: result_2 -- name: TEST.5a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync +- name: TEST.2a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: timeout: 60 - when: result.changed == true + when: result_2.changed == true -- name: TEST.5b - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.2b - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_2b until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_2b.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.2c - OVERRIDDEN - [debug] print result_2 + debug: + var: result_2 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - '(result.response[1].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[1].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - '"{{ switch_1 }}" in result.diff[0].attach[0].ip_address' - - '"{{ switch_2 }}" in result.diff[0].attach[1].ip_address' - - 'result.diff[0].vrf_name == "ansible-vrf-int2"' - -- name: TEST.6 - OVERRIDDEN - [overridden] Override vrf_lite extension with new dot1q value - cisco.dcnm.dcnm_vrf: &conf2 + - 'result_2.changed == true' + - 'result_2.response[0].RETURN_CODE == 200' + - 'result_2.response[1].RETURN_CODE == 200' + - 'result_2.response[2].RETURN_CODE == 200' + - '(result_2.response[1].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_2.response[1].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_2.diff[0].attach[0].deploy == true' + - 'result_2.diff[0].attach[1].deploy == true' + - '"{{ switch_1 }}" in result_2.diff[0].attach[0].ip_address' + - '"{{ switch_2 }}" in result_2.diff[0].attach[1].ip_address' + - 'result_2.diff[0].vrf_name == "ansible-vrf-int2"' + +- name: TEST.3 - OVERRIDDEN - [overridden] Override vrf_lite extension with new dot1q value + cisco.dcnm.dcnm_vrf: &conf3 fabric: "{{ fabric_1 }}" state: overridden config: @@ -263,64 +281,72 @@ neighbor_ipv4: 10.33.0.0 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 21 # dot1q can be got from dcnm + dot1q: 21 # optional controller can provide deploy: true - register: result + register: result_3 -- name: TEST.6a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync +- name: TEST.3a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: timeout: 60 - when: result.changed == true + when: result_3.changed == true -# In step TEST.6, NDFC issues an NX-OS CLI that immediately switches from +# In step TEST.3, NDFC issues an NX-OS CLI that immediately switches # from configure profile mode, to configure terminal; vrf context . # This command results in FAILURE (switch accounting log). Adding a # wait_for will not help since this all happens within step TEST.6. # A config-deploy resolves the OUT-OF-SYNC VRF status. -- name: TEST.6b - OVERRIDDEN - [dcnm_rest.POST] - config-deploy to workaround FAILED/OUT-OF-SYNC VRF status +- name: TEST.3b - OVERRIDDEN - [dcnm_rest.POST] - config-deploy to workaround FAILED/OUT-OF-SYNC VRF status cisco.dcnm.dcnm_rest: method: POST path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_1 }}/config-deploy?forceShowRun=false" - when: result.changed == true + when: result_3.changed == true -- name: TEST.6c - OVERRIDDEN - [wait_for] Wait 60 for vrfStatus == DEPLOYED +- name: TEST.3c - OVERRIDDEN - [wait_for] Wait 60 for vrfStatus == DEPLOYED wait_for: timeout: 60 - when: result.changed == true + when: result_3.changed == true -- name: TEST.6d - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.3d - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_3d until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_3d.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.3e - OVERRIDDEN - [debug] print result_3 + debug: + var: result_3 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int2"' - -- name: TEST.7 - OVERRIDDEN - [overridden] conf2 - Idempotence - cisco.dcnm.dcnm_vrf: *conf2 - register: result + - 'result_3.changed == true' + - 'result_3.response[0].RETURN_CODE == 200' + - 'result_3.response[1].RETURN_CODE == 200' + - '(result_3.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - 'result_3.diff[0].attach[0].deploy == true' + - 'result_3.diff[0].vrf_name == "ansible-vrf-int2"' + +- name: TEST.3f - OVERRIDDEN - [overridden] conf2 - Idempotence + cisco.dcnm.dcnm_vrf: *conf3 + register: result_3f + +- name: TEST.3g - OVERRIDDEN - [debug] print result_3f + debug: + var: result_3f - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_3f.changed == false' + - 'result_3f.response|length == 0' -- name: TEST.8 - OVERRIDDEN - [overridden] Override ansible-vrf-int2 to create ansible-vrf-int1 with LITE Extension - cisco.dcnm.dcnm_vrf: &conf3 +- name: TEST.4 - OVERRIDDEN - [overridden] Override ansible-vrf-int2 to create ansible-vrf-int1 with LITE Extension + cisco.dcnm.dcnm_vrf: &conf4 fabric: "{{ fabric_1 }}" state: overridden config: @@ -339,57 +365,67 @@ neighbor_ipv4: 10.33.0.0 # optional ipv6_addr: 2010::10:34:0:1/64 # optional neighbor_ipv6: 2010::10:34:0:2 # optional - dot1q: 31 # dot1q can be got from dcnm + dot1q: 31 # optional controller can provide deploy: true - register: result + register: result_4 -- name: TEST.8a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync +- name: TEST.4a - OVERRIDDEN - [wait_for] Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: timeout: 60 - when: result.changed == true + when: result_4.changed == true -- name: TEST.8b - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED +- name: TEST.4b - OVERRIDDEN - [query] Wait for vrfStatus == DEPLOYED cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: query - register: query_result + register: result_4b until: - - "query_result.response[0].parent.vrfStatus is search('DEPLOYED')" + - "result_4b.response[0].parent.vrfStatus is search('DEPLOYED')" retries: 30 delay: 2 +- name: TEST.4c - OVERRIDDEN - [debug] print result_4 + debug: + var: result_4 + - assert: that: - - 'result.changed == true' - - 'result.response[0].RETURN_CODE == 200' - - 'result.response[1].RETURN_CODE == 200' - - 'result.response[2].RETURN_CODE == 200' - - 'result.response[3].RETURN_CODE == 200' - - 'result.response[4].RETURN_CODE == 200' - - 'result.response[5].RETURN_CODE == 200' - - '(result.response[0].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[0].DATA|dict2items)[1].value == "SUCCESS"' - - '(result.response[4].DATA|dict2items)[0].value == "SUCCESS"' - - '(result.response[4].DATA|dict2items)[1].value == "SUCCESS"' - - 'result.response[2].METHOD == "DELETE"' - - 'result.diff[0].attach[0].deploy == true' - - 'result.diff[0].attach[1].deploy == true' - - 'result.diff[0].vrf_name == "ansible-vrf-int1"' - - 'result.diff[1].attach[0].deploy == false' - - 'result.diff[1].attach[1].deploy == false' - - 'result.diff[1].vrf_name == "ansible-vrf-int2"' - -- name: TEST.9 - OVERRIDDEN - [overridden] conf3 - Idempotence - cisco.dcnm.dcnm_vrf: *conf3 - register: result + - 'result_4.changed == true' + - 'result_4.response[0].RETURN_CODE == 200' + - 'result_4.response[1].RETURN_CODE == 200' + - 'result_4.response[2].RETURN_CODE == 200' + - 'result_4.response[3].RETURN_CODE == 200' + - 'result_4.response[4].RETURN_CODE == 200' + - 'result_4.response[5].RETURN_CODE == 200' + - 'result_4.response[6].RETURN_CODE == 200' + - 'result_4.response[7].RETURN_CODE == 200' + - '(result_4.response[0].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_4.response[0].DATA|dict2items)[1].value == "SUCCESS"' + - '(result_4.response[6].DATA|dict2items)[0].value == "SUCCESS"' + - '(result_4.response[6].DATA|dict2items)[1].value == "SUCCESS"' + - 'result_4.response[2].METHOD == "DELETE"' + - 'result_4.diff[0].attach[0].deploy == true' + - 'result_4.diff[0].attach[1].deploy == true' + - 'result_4.diff[0].vrf_name == "ansible-vrf-int1"' + - 'result_4.diff[1].attach[0].deploy == false' + - 'result_4.diff[1].attach[1].deploy == false' + - 'result_4.diff[1].vrf_name == "ansible-vrf-int2"' + +- name: TEST.4d - OVERRIDDEN - [overridden] conf3 - Idempotence + cisco.dcnm.dcnm_vrf: *conf4 + register: result_4d + +- name: TEST.4e - OVERRIDDEN - [debug] print result_4d + debug: + var: result_4d - assert: that: - - 'result.changed == false' - - 'result.response|length == 0' + - 'result_4d.changed == false' + - 'result_4d.response|length == 0' ############################################## ## CLEAN-UP ## diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml index f92d99bb7..8a3e7f7ff 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/query.yaml @@ -56,12 +56,12 @@ state: deleted register: result_setup_2 -- name: SETUP.2a - QUERY - Wait 40 seconds for controller and switch to sync +- name: SETUP.2a - QUERY - Wait 60 seconds for controller and switch to sync # The vrf lite profile removal returns ok for deployment, but the switch # takes time to remove the profile so wait for some time before creating # a new vrf, else the switch goes into OUT-OF-SYNC state wait_for: - timeout: 40 + timeout: 60 when: result_setup_2.changed == true - name: SETUP.3 - QUERY - [merged] Create, Attach, Deploy VLAN+VRF ansible-vrf-int1 @@ -235,7 +235,7 @@ neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 2 # dot1q can be got from dcnm + dot1q: 2 # optional controller can provide deploy: true register: result_4 @@ -283,7 +283,7 @@ neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 2 # dot1q can be got from dcnm + dot1q: 2 # optional controller can provide deploy: true register: result_5 diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml index caacdeb74..a0a8254d5 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/replaced.yaml @@ -91,6 +91,10 @@ ansible.builtin.debug: var: result_setup_4 +- name: SETUP.4c - REPLACED - [debug] print result_setup_4 + debug: + var: result_setup_4 + - assert: that: - 'result_setup_4.changed == true' @@ -220,7 +224,7 @@ wait_for: timeout: 60 -- name: TEST.3 - REPLACED - [merged] Create, Attach, Deploy VLAN+VRF LITE EXTENSION switch_2 (user provided) +- name: TEST.3 - REPLACED - [merged] Create, Attach, Deploy VLAN+VRF+LITE switch_2 (user provided VLAN) cisco.dcnm.dcnm_vrf: fabric: "{{ fabric_1 }}" state: merged @@ -240,7 +244,7 @@ neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 2 # dot1q can be got from dcnm + dot1q: 2 # optional controller can provide deploy: true register: result_3 @@ -272,7 +276,7 @@ - '"{{ switch_2 }}" in result_3.diff[0].attach[1].ip_address' - 'result_3.diff[0].vrf_name == "ansible-vrf-int1"' -- name: TEST.4 - REPLACED - [replaced] Update existing VRF LITE extensions using Replace - Delete VRF LITE Attachment Only +- name: TEST.4 - REPLACED - [replaced] Update existing VRF - Delete VRF LITE Attachment cisco.dcnm.dcnm_vrf: &conf4 fabric: "{{ fabric_1 }}" state: replaced @@ -326,7 +330,7 @@ that: - 'result_4d.changed == false' -- name: TEST.5 - REPLACED - [replaced] Update existing VRF LITE extensions using Replace - Create VRF LITE Attachment Only +- name: TEST.5 - REPLACED - [replaced] Update existing VRF - Create VRF LITE Attachment cisco.dcnm.dcnm_vrf: &conf5 fabric: "{{ fabric_1 }}" state: replaced @@ -346,7 +350,7 @@ neighbor_ipv4: 10.33.0.1 # optional ipv6_addr: 2010::10:34:0:7/64 # optional neighbor_ipv6: 2010::10:34:0:3 # optional - dot1q: 2 # dot1q can be got from dcnm + dot1q: 2 # optional controller can provide deploy: true register: result_5 From e92c1e2b556f660a9fdf8d0c052d3ce68b546e55 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Tue, 17 Dec 2024 13:13:37 -1000 Subject: [PATCH 49/49] IT: Update comment for workaround. Update the comment for test 3b to indicate that the workaround is needed only when Overlay Mode is set to "config-profile" (which is the default for new fabrics). The issue does not happen when Overlay Mode is set to "cli". --- tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml index c87515724..8e2de4f0a 100644 --- a/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml +++ b/tests/integration/targets/dcnm_vrf/tests/dcnm/overridden.yaml @@ -298,6 +298,9 @@ # This command results in FAILURE (switch accounting log). Adding a # wait_for will not help since this all happens within step TEST.6. # A config-deploy resolves the OUT-OF-SYNC VRF status. +# NOTE: this happens ONLY when Fabric Settings -> Advanced -> Overlay Mode +# is set to "config-profile" (which is the default). It does not happen +# if Overlay Mode is set to "cli". - name: TEST.3b - OVERRIDDEN - [dcnm_rest.POST] - config-deploy to workaround FAILED/OUT-OF-SYNC VRF status cisco.dcnm.dcnm_rest: method: POST