From 759f4fb1ff93dd367f58271e6ec212a244b70428 Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Wed, 27 Sep 2023 20:29:01 +0530 Subject: [PATCH 1/9] Support attach network to paired TOR switch --- plugins/modules/dcnm_network.py | 169 +++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 4 deletions(-) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index c0e995e7e..7b82aee00 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -256,6 +256,24 @@ There will not be any functional impact if specified in playbook. type: bool default: true + tor_ports: + description: + - List of interfaces in the paired TOR switch for this leaf where the network will be attached + type: list + elements: dict + required: false + suboptions: + ip_address: + description: + - IP address of the TOR switch where the network will be attached + type: str + required: true + ports: + description: + - List of TOR switch interfaces where the network will be attached + type: list + elements: str + required: true deploy: description: - Global knob to control whether to deploy the attachment @@ -551,6 +569,62 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if want.get("isAttached") is not None: if bool(have["isAttached"]) and bool(want["isAttached"]): + torports_configured = False + + # Handle tor ports first if configured. + if want.get("torports"): + for tor_w in want["torports"]: + torports_present = False + for tor_h in have.get("torports"): + if tor_w["switch"] is tor_h["switch"]: + torports_present = True + h_tor_ports = ( + tor_h["torPorts"].split(",") + if have["torPorts"] + else [] + ) + w_tor_ports = ( + tor_w["torPorts"].split(",") + if want["torPorts"] + else [] + ) + + if sorted(h_tor_ports) != sorted(w_tor_ports): + atch_tor_ports = list( + set(w_tor_ports) - set(h_tor_ports) + ) + + if replace: + atch_tor_ports = w_tor_ports + else: + atch_tor_ports.extend(h_tor_ports) + + torconfig = tor_w["switch"] + "(" + ",".join(atch_tor_ports) + ")" + want.update({"torPorts": torconfig}) + # Update torports_configured to True. If there is no other config change for attach + # We will still append this attach to attach_list as there is tor port change + torports_configured = True + + if not torports_present: + torconfig = tor_w["switch"] + "(" + tor_w["torPorts"] + ")" + want.update({"torPorts": torconfig}) + # Update torports_configured to True. If there is no other config change for attach + # We will still append this attach to attach_list as there is tor port change + torports_configured = True + + if have.get("torports"): + del have["torports"] + + elif have.get("torports"): + # Dont update torports_configured to True. + # If at all there is any other config change, this attach to will be appended attach_list there + for tor_h in have.get("torports"): + torconfig = tor_h["switch"] + "(" + tor_h["torPorts"] + ")" + want.update({"torPorts": torconfig}) + del have["torports"] + + del want["torports"] + h_sw_ports = ( have["switchPorts"].split(",") if have["switchPorts"] @@ -580,6 +654,12 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): ) if not atch_sw_ports and not dtach_sw_ports: + if torports_configured: + del want["isAttached"] + attach_list.append(want) + if bool(want["is_deploy"]): + dep_net = True + continue want.update( @@ -608,11 +688,25 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if not atch_sw_ports: # The attachments in the have consist of attachments in want and more. + if torports_configured: + del want["isAttached"] + attach_list.append(want) + if bool(want["is_deploy"]): + dep_net = True + continue + else: + want.update( + {"switchPorts": ",".join(atch_sw_ports)} + ) - want.update( - {"switchPorts": ",".join(atch_sw_ports)} - ) + del want["isAttached"] + attach_list.append(want) + if bool(want["is_deploy"]): + dep_net = True + continue + + elif torports_configured: del want["isAttached"] attach_list.append(want) if bool(want["is_deploy"]): @@ -631,6 +725,11 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): dep_net = True continue del want["isAttached"] + if want.get("torports"): + for tor_w in want["torports"]: + torconfig = tor_w["switch"] + "(" + tor_w["torPorts"] + ")" + want.update({"torPorts": torconfig}) + del want["torports"] want.update({"deployment": True}) attach_list.append(want) if bool(want["is_deploy"]): @@ -650,6 +749,11 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if not found: if bool(want["isAttached"]): + if want.get("torports"): + for tor_w in want["torports"]: + torconfig = tor_w["switch"] + "(" + tor_w["torPorts"] + ")" + want.update({"torPorts": torconfig}) + del want["torports"] del want["isAttached"] want["deployment"] = True attach_list.append(want) @@ -680,10 +784,13 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): attach_list.append(havtoattach) break + # self.module.fail_json(msg="attach done") + return attach_list, dep_net def update_attach_params(self, attach, net_name, deploy): + torlist = [] if not attach: return {} @@ -728,11 +835,27 @@ def update_attach_params(self, attach, net_name, deploy): attach.update({"instanceValues": ""}) attach.update({"freeformConfig": ""}) attach.update({"is_deploy": deploy}) + if attach.get("tor_ports"): + torports = {} + if role.lower() != "leaf": + msg = "tor_ports for Networks cannot be attached to switch {0} with role {1}".format( + attach["ip_address"], role + ) + self.module.fail_json(msg=msg) + for tor in attach.get("tor_ports"): + #torports.update({"ip_address": tor["ip_address"]}) + torports.update({"switch": self.inventory_data[tor["ip_address"]].get("logicalName")}) + torports.update({"torPorts": ",".join(tor["ports"])}) + torlist.append(torports) + del attach["tor_ports"] + attach.update({"torports": torlist}) + if "deploy" in attach: del attach["deploy"] del attach["ports"] del attach["ip_address"] + #self.module.fail_json(msg="attach done") return attach def diff_for_create(self, want, have): @@ -1351,6 +1474,7 @@ def get_have(self): attach_list = net_attach["lanAttachList"] dep_net = "" for attach in attach_list: + torlist = [] attach_state = False if attach["lanAttachState"] == "NA" else True deploy = attach["isLanAttached"] deployed = False @@ -1367,7 +1491,21 @@ def get_have(self): sn = attach["switchSerialNo"] vlan = attach["vlanId"] - ports = attach["portNames"] + + if attach["portNames"] and re.match("\S+\(\S+\d+\/\d+\)", attach["portNames"]): + for idx, s in enumerate(re.findall("\S+\(\S+\d+\/\d+\)", attach["portNames"])): + torports = {} + t = s.split("(") + k = t[1].split(")") + if idx == 0: + ports = k[0] + continue + torports.update({"switch": t[0]}) + torports.update({"torPorts": k[0]}) + torlist.append(torports) + attach.update({"torports": torlist}) + else: + ports = attach["portNames"] # The deletes and updates below are done to update the incoming dictionary format to # match to what the outgoing payload requirements mandate. @@ -1962,6 +2100,11 @@ def get_diff_merge(self, replace=False): for attach in want_a["lanAttachList"]: # Saftey check if attach.get("isAttached"): + if attach.get("torports"): + for tor_w in attach["torports"]: + torconfig = tor_w["switch"] + "(" + tor_w["torPorts"] + ")" + attach.update({"torPorts": torconfig}) + del attach["torports"] del attach["isAttached"] atch_list.append(attach) if atch_list: @@ -2613,6 +2756,11 @@ def validate_input(self): ip_address=dict(required=True, type="str"), ports=dict(required=True, type="list"), deploy=dict(type="bool", default=True), + tor_ports=dict(required=False, type="list", elements="dict"), + ) + tor_att_spec = dict( + ip_address=dict(required=True, type="str"), + ports=dict(required=False, type="list", default=[]), ) if self.config: @@ -2631,6 +2779,19 @@ def validate_input(self): attach["deploy"] = net["deploy"] if attach.get("ports"): attach["ports"] = [port.capitalize() for port in attach["ports"]] + if attach.get("tor_ports"): + if self.dcnm_version == 11: + invalid_params.append( + "tor_port configurations are supported only on NDFC" + ) + valid_tor_att, invalid_tor_att = validate_list_of_dicts( + attach["tor_ports"], tor_att_spec + ) + attach["tor_ports"] = valid_tor_att + for tor in attach["tor_ports"]: + if tor.get("ports"): + tor["ports"] = [port.capitalize() for port in tor["ports"]] + invalid_params.extend(invalid_tor_att) invalid_params.extend(invalid_att) if state != "deleted": From 6d9fe903f1f8027a373210b7452a877d61cab1bc Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Wed, 27 Sep 2023 21:19:55 +0530 Subject: [PATCH 2/9] Support attach network to paired TOR switch - Added example --- plugins/modules/dcnm_network.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index 7b82aee00..41e06a4aa 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -350,6 +350,9 @@ attach: - ip_address: 192.168.1.224 ports: [Ethernet1/11, Ethernet1/12] + tor_ports: + - ip_address: 192.168.1.120 + ports: [Ethernet1/14, Ethernet1/15] - ip_address: 192.168.1.225 ports: [Ethernet1/11, Ethernet1/12] deploy: false From 0d6dfe549a0531221867fbf884f22598657dd8e9 Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Fri, 29 Sep 2023 14:45:39 +0530 Subject: [PATCH 3/9] Support attach network to paired TOR switch - Crash fix --- plugins/modules/dcnm_network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index 41e06a4aa..bba46663f 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -626,7 +626,8 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want.update({"torPorts": torconfig}) del have["torports"] - del want["torports"] + if want.get("torports"): + del want["torports"] h_sw_ports = ( have["switchPorts"].split(",") From d494d199f9053ae661dbfa21dc54f75badb80470 Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Wed, 4 Oct 2023 12:00:37 +0530 Subject: [PATCH 4/9] Support attach network to paired TOR switch - Crash fix 1 --- plugins/modules/dcnm_network.py | 72 +++++++++++++++++---------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index bba46663f..32cad2545 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -259,6 +259,7 @@ tor_ports: description: - List of interfaces in the paired TOR switch for this leaf where the network will be attached + - Please attach the same set of TOR ports to both the VPC paired switches. type: list elements: dict required: false @@ -578,35 +579,36 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): if want.get("torports"): for tor_w in want["torports"]: torports_present = False - for tor_h in have.get("torports"): - if tor_w["switch"] is tor_h["switch"]: - torports_present = True - h_tor_ports = ( - tor_h["torPorts"].split(",") - if have["torPorts"] - else [] - ) - w_tor_ports = ( - tor_w["torPorts"].split(",") - if want["torPorts"] - else [] - ) - - if sorted(h_tor_ports) != sorted(w_tor_ports): - atch_tor_ports = list( - set(w_tor_ports) - set(h_tor_ports) + if have.get("torports"): + for tor_h in have["torports"]: + if tor_w["switch"] is tor_h["switch"]: + torports_present = True + h_tor_ports = ( + tor_h["torPorts"].split(",") + if have["torPorts"] + else [] + ) + w_tor_ports = ( + tor_w["torPorts"].split(",") + if want["torPorts"] + else [] ) - if replace: - atch_tor_ports = w_tor_ports - else: - atch_tor_ports.extend(h_tor_ports) + if sorted(h_tor_ports) != sorted(w_tor_ports): + atch_tor_ports = list( + set(w_tor_ports) - set(h_tor_ports) + ) - torconfig = tor_w["switch"] + "(" + ",".join(atch_tor_ports) + ")" - want.update({"torPorts": torconfig}) - # Update torports_configured to True. If there is no other config change for attach - # We will still append this attach to attach_list as there is tor port change - torports_configured = True + if replace: + atch_tor_ports = w_tor_ports + else: + atch_tor_ports.extend(h_tor_ports) + + torconfig = tor_w["switch"] + "(" + ",".join(atch_tor_ports) + ")" + want.update({"torPorts": torconfig}) + # Update torports_configured to True. If there is no other config change for attach + # We will still append this attach to attach_list as there is tor port change + torports_configured = True if not torports_present: torconfig = tor_w["switch"] + "(" + tor_w["torPorts"] + ")" @@ -1497,15 +1499,15 @@ def get_have(self): vlan = attach["vlanId"] if attach["portNames"] and re.match("\S+\(\S+\d+\/\d+\)", attach["portNames"]): - for idx, s in enumerate(re.findall("\S+\(\S+\d+\/\d+\)", attach["portNames"])): + for idx, sw_list in enumerate(re.findall("\S+\(\S+\d+\/\d+\)", attach["portNames"])): torports = {} - t = s.split("(") - k = t[1].split(")") + sw = sw_list.split("(") + eth_list = sw[1].split(")") if idx == 0: - ports = k[0] + ports = eth_list[0] continue - torports.update({"switch": t[0]}) - torports.update({"torPorts": k[0]}) + torports.update({"switch": sw[0]}) + torports.update({"torPorts": eth_list[0]}) torlist.append(torports) attach.update({"torports": torlist}) else: @@ -2785,9 +2787,9 @@ def validate_input(self): attach["ports"] = [port.capitalize() for port in attach["ports"]] if attach.get("tor_ports"): if self.dcnm_version == 11: - invalid_params.append( - "tor_port configurations are supported only on NDFC" - ) + msg = "Invalid parameters in playbook: tor_ports configurations are supported only on NDFC" + self.module.fail_json(msg=msg) + valid_tor_att, invalid_tor_att = validate_list_of_dicts( attach["tor_ports"], tor_att_spec ) From 49725acb1a2fdb3987fc360a9003ea0dbc413c21 Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Wed, 4 Oct 2023 19:20:27 +0530 Subject: [PATCH 5/9] Support attach network to paired TOR switch - Idempotence fix --- plugins/modules/dcnm_network.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index 32cad2545..36f5cf992 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -581,16 +581,17 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): torports_present = False if have.get("torports"): for tor_h in have["torports"]: - if tor_w["switch"] is tor_h["switch"]: + if tor_w["switch"] == tor_h["switch"]: + atch_tor_ports = [] torports_present = True h_tor_ports = ( tor_h["torPorts"].split(",") - if have["torPorts"] + if tor_h["torPorts"] else [] ) w_tor_ports = ( tor_w["torPorts"].split(",") - if want["torPorts"] + if tor_w["torPorts"] else [] ) @@ -608,7 +609,8 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want.update({"torPorts": torconfig}) # Update torports_configured to True. If there is no other config change for attach # We will still append this attach to attach_list as there is tor port change - torports_configured = True + if atch_tor_ports != h_tor_ports: + torports_configured = True if not torports_present: torconfig = tor_w["switch"] + "(" + tor_w["torPorts"] + ")" @@ -2241,6 +2243,8 @@ def format_diff(self): found_c["attach"].append(detach_d) attach_d.update({"ports": a_w["switchPorts"]}) attach_d.update({"deploy": a_w["deployment"]}) + if a_w["torPorts"]: + attach_d.update({"tor_ports": a_w["torPorts"]}) found_c["attach"].append(attach_d) diff.append(found_c) @@ -2267,6 +2271,8 @@ def format_diff(self): new_attach_list.append(detach_d) attach_d.update({"ports": a_w["switchPorts"]}) attach_d.update({"deploy": a_w["deployment"]}) + if a_w["torPorts"]: + attach_d.update({"tor_ports": a_w["torPorts"]}) new_attach_list.append(attach_d) if new_attach_list: From 167c6582fe69f17ab0158b1089d3701503c56341 Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Thu, 5 Oct 2023 18:04:47 +0530 Subject: [PATCH 6/9] Support attach network to paired TOR switch - Diff gen fix --- plugins/modules/dcnm_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index 36f5cf992..a66d4a772 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -2243,7 +2243,7 @@ def format_diff(self): found_c["attach"].append(detach_d) attach_d.update({"ports": a_w["switchPorts"]}) attach_d.update({"deploy": a_w["deployment"]}) - if a_w["torPorts"]: + if a_w.get("torPorts"): attach_d.update({"tor_ports": a_w["torPorts"]}) found_c["attach"].append(attach_d) @@ -2271,7 +2271,7 @@ def format_diff(self): new_attach_list.append(detach_d) attach_d.update({"ports": a_w["switchPorts"]}) attach_d.update({"deploy": a_w["deployment"]}) - if a_w["torPorts"]: + if a_w.get("torPorts"): attach_d.update({"tor_ports": a_w["torPorts"]}) new_attach_list.append(attach_d) From e034e03a4d6089583b07037c83f58965e3333373 Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Thu, 5 Oct 2023 22:40:28 +0530 Subject: [PATCH 7/9] Support attach network to paired TOR switch - IT/UT --- docs/cisco.dcnm.dcnm_network_module.rst | 144 +++++-- plugins/modules/dcnm_network.py | 6 +- .../tor_ports_networks.yaml | 366 ++++++++++++++++++ .../modules/dcnm/fixtures/dcnm_network.json | 160 +++++++- tests/unit/modules/dcnm/test_dcnm_network.py | 156 ++++++++ 5 files changed, 785 insertions(+), 47 deletions(-) create mode 100644 tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/tor_ports_networks.yaml diff --git a/docs/cisco.dcnm.dcnm_network_module.rst b/docs/cisco.dcnm.dcnm_network_module.rst index de22372e0..b5d972252 100644 --- a/docs/cisco.dcnm.dcnm_network_module.rst +++ b/docs/cisco.dcnm.dcnm_network_module.rst @@ -30,12 +30,12 @@ Parameters - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterParameter Choices/Defaults Comments
+
config @@ -52,7 +52,7 @@ Parameters
+
arp_suppress @@ -73,7 +73,7 @@ Parameters
+
attach @@ -91,7 +91,7 @@ Parameters
+
deploy @@ -113,7 +113,7 @@ Parameters
+
ip_address @@ -131,7 +131,7 @@ Parameters
+
ports @@ -147,10 +147,69 @@ Parameters
List of switch interfaces where the network will be attached
+
+ tor_ports + +
+ list + / elements=dictionary +
+
+ +
List of interfaces in the paired TOR switch for this leaf where the network will be attached
+
Please attach the same set of TOR ports to both the VPC paired switches.
+
+
+ ip_address + +
+ string + / required +
+
+ +
IP address of the TOR switch where the network will be attached
+
+
+ ports + +
+ list + / elements=string + / required +
+
+ +
List of TOR switch interfaces where the network will be attached
+
deploy @@ -174,7 +233,7 @@ Parameters
+
dhcp_loopback_id @@ -191,7 +250,7 @@ Parameters
+
dhcp_srvr1_ip @@ -207,7 +266,7 @@ Parameters
+
dhcp_srvr1_vrf @@ -223,7 +282,7 @@ Parameters
+
dhcp_srvr2_ip @@ -239,7 +298,7 @@ Parameters
+
dhcp_srvr2_vrf @@ -255,7 +314,7 @@ Parameters
+
dhcp_srvr3_ip @@ -271,7 +330,7 @@ Parameters
+
dhcp_srvr3_vrf @@ -287,7 +346,7 @@ Parameters
+
gw_ip_subnet @@ -303,7 +362,7 @@ Parameters
+
gw_ipv6_subnet @@ -319,7 +378,7 @@ Parameters
+
int_desc @@ -335,7 +394,7 @@ Parameters
+
intfvlan_nf_monitor @@ -353,7 +412,7 @@ Parameters
+
is_l2only @@ -374,7 +433,7 @@ Parameters
+
l3gw_on_border @@ -394,7 +453,7 @@ Parameters
+
mtu_l3intf @@ -411,7 +470,7 @@ Parameters
+
multicast_group_address @@ -427,7 +486,7 @@ Parameters
+
net_extension_template @@ -444,7 +503,7 @@ Parameters
+
net_id @@ -461,7 +520,7 @@ Parameters
+
net_name @@ -478,7 +537,7 @@ Parameters
+
net_template @@ -495,7 +554,7 @@ Parameters
+
netflow_enable @@ -517,7 +576,7 @@ Parameters
+
route_target_both @@ -537,7 +596,7 @@ Parameters
+
routing_tag @@ -554,7 +613,7 @@ Parameters
+
secondary_ip_gw1 @@ -570,7 +629,7 @@ Parameters
+
secondary_ip_gw2 @@ -586,7 +645,7 @@ Parameters
+
secondary_ip_gw3 @@ -602,7 +661,7 @@ Parameters
+
secondary_ip_gw4 @@ -618,7 +677,7 @@ Parameters
+
trm_enable @@ -638,7 +697,7 @@ Parameters
+
vlan_id @@ -655,7 +714,7 @@ Parameters
+
vlan_name @@ -672,7 +731,7 @@ Parameters
+
vlan_nf_monitor @@ -690,7 +749,7 @@ Parameters
+
vrf_name @@ -707,7 +766,7 @@ Parameters
+
fabric @@ -723,7 +782,7 @@ Parameters
+
state @@ -817,6 +876,9 @@ Examples attach: - ip_address: 192.168.1.224 ports: [Ethernet1/11, Ethernet1/12] + tor_ports: + - ip_address: 192.168.1.120 + ports: [Ethernet1/14, Ethernet1/15] - ip_address: 192.168.1.225 ports: [Ethernet1/11, Ethernet1/12] deploy: false diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index a66d4a772..6e281853a 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -851,7 +851,6 @@ def update_attach_params(self, attach, net_name, deploy): ) self.module.fail_json(msg=msg) for tor in attach.get("tor_ports"): - #torports.update({"ip_address": tor["ip_address"]}) torports.update({"switch": self.inventory_data[tor["ip_address"]].get("logicalName")}) torports.update({"torPorts": ",".join(tor["ports"])}) torlist.append(torports) @@ -863,7 +862,6 @@ def update_attach_params(self, attach, net_name, deploy): del attach["ports"] del attach["ip_address"] - #self.module.fail_json(msg="attach done") return attach def diff_for_create(self, want, have): @@ -1500,8 +1498,8 @@ def get_have(self): sn = attach["switchSerialNo"] vlan = attach["vlanId"] - if attach["portNames"] and re.match("\S+\(\S+\d+\/\d+\)", attach["portNames"]): - for idx, sw_list in enumerate(re.findall("\S+\(\S+\d+\/\d+\)", attach["portNames"])): + if attach["portNames"] and re.match(r"\S+\(\S+\d+\/\d+\)", attach["portNames"]): + for idx, sw_list in enumerate(re.findall(r"\S+\(\S+\d+\/\d+\)", attach["portNames"])): torports = {} sw = sw_list.split("(") eth_list = sw[1].split(")") diff --git a/tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/tor_ports_networks.yaml b/tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/tor_ports_networks.yaml new file mode 100644 index 000000000..e5ddfb905 --- /dev/null +++ b/tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/tor_ports_networks.yaml @@ -0,0 +1,366 @@ +############################################## +## SETUP ## +############################################## + +- block: + - set_fact: + rest_path: "/rest/control/fabrics/{{ test_fabric }}" + when: controller_version == "11" + + - set_fact: + rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" + when: controller_version >= "12" + + - name: SCALE - Verify if fabric is deployed. + cisco.dcnm.dcnm_rest: + method: GET + path: "{{ rest_path }}" + register: result + + - assert: + that: + - 'result.response.DATA != None' + + - name: SCALE - Clean up any existing networks + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: deleted + +############################################## +## TESTS ## +############################################## + + - name: TOR - Create, Attach and Deploy Single Network with Multiple Switch and TOR Attach + cisco.dcnm.dcnm_network: &conf1 + fabric: "{{ test_fabric }}" + state: merged + config: + - net_name: ansible-net13 + vrf_name: Tenant-1 + net_id: 7005 + net_template: Default_Network_Universal + net_extension_template: Default_Network_Extension_Universal + vlan_id: 1500 + gw_ip_subnet: '192.168.30.1/24' + attach: + - ip_address: "{{ ansible_switch1 }}" + ports: ["{{ ansible_sw1_int1 }}", "{{ ansible_sw1_int2 }}"] + tor_ports: + - ip_address: "{{ ansible_tor_switch1 }}" + ports: ["{{ ansible_tor_int1 }}", "{{ ansible_tor_int2 }}"] + - ip_address: "{{ ansible_switch2 }}" + ports: ["{{ ansible_sw2_int5 }}", "{{ ansible_sw2_int6 }}"] + deploy: True + register: result + + - name: Query fabric state until networkStatus transitions to DEPLOYED state + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: query + register: query_result + until: + - "query_result.response[0].parent.networkStatus is search('DEPLOYED|PENDING')" + 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' + - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ ansible_tor_int1 }}" in result.diff[0].attach[0].tor_ports' + - '"{{ ansible_tor_int2 }}" in result.diff[0].attach[0].tor_ports' + - 'result.diff[0].attach[1].deploy == true' + - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - 'result.diff[0].net_name == "ansible-net13"' + - 'result.diff[0].net_id == 7005' + - 'result.diff[0].vrf_name == "Tenant-1"' + + - name: TOR - conf1 - Idempotence + cisco.dcnm.dcnm_network: *conf1 + register: result + + - assert: + that: + - 'result.changed == false' + - 'result.response|length == 0' + + - name: TOR - Attach new TOR ports to already Attached TOR ports + cisco.dcnm.dcnm_network: &conf2 + fabric: "{{ test_fabric }}" + state: merged + config: + - net_name: ansible-net13 + vrf_name: Tenant-1 + net_id: 7005 + net_template: Default_Network_Universal + net_extension_template: Default_Network_Extension_Universal + vlan_id: 1500 + gw_ip_subnet: '192.168.30.1/24' + attach: + - ip_address: "{{ ansible_switch1 }}" + ports: ["{{ ansible_sw1_int1 }}", "{{ ansible_sw1_int2 }}"] + tor_ports: + - ip_address: "{{ ansible_tor_switch1 }}" + ports: ["{{ ansible_tor_int3 }}"] + - ip_address: "{{ ansible_switch2 }}" + ports: ["{{ ansible_sw2_int5 }}", "{{ ansible_sw2_int6 }}"] + deploy: True + register: result + + - name: Query fabric state until networkStatus transitions to DEPLOYED state + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: query + register: query_result + until: + - "query_result.response[0].parent.networkStatus is search('DEPLOYED|PENDING')" + retries: 30 + delay: 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.diff[0].attach[0].deploy == true' + - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ ansible_tor_int1 }}" in result.diff[0].attach[0].tor_ports' + - '"{{ ansible_tor_int2 }}" in result.diff[0].attach[0].tor_ports' + - '"{{ ansible_tor_int3 }}" in result.diff[0].attach[0].tor_ports' + - 'result.diff[0].net_name == "ansible-net13"' + + - name: TOR - conf2 - Idempotence + cisco.dcnm.dcnm_network: *conf2 + register: result + + - assert: + that: + - 'result.changed == false' + - 'result.response|length == 0' + + - name: TOR - setup - Clean up any existing network + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: deleted + + - name: TOR - Create, Attach and Deploy Single Network with Multiple Switch Attach + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: merged + config: + - net_name: ansible-net13 + vrf_name: Tenant-1 + net_id: 7005 + net_template: Default_Network_Universal + net_extension_template: Default_Network_Extension_Universal + vlan_id: 1500 + gw_ip_subnet: '192.168.30.1/24' + attach: + - ip_address: "{{ ansible_switch1 }}" + ports: ["{{ ansible_sw1_int1 }}", "{{ ansible_sw1_int2 }}"] + - ip_address: "{{ ansible_switch2 }}" + ports: ["{{ ansible_sw2_int5 }}", "{{ ansible_sw2_int6 }}"] + deploy: True + register: result + + - name: Query fabric state until networkStatus transitions to DEPLOYED state + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: query + register: query_result + until: + - "query_result.response[0].parent.networkStatus is search('DEPLOYED|PENDING')" + 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' + - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' + - 'result.diff[0].attach[1].deploy == true' + - '"{{ ansible_switch2 }}" in result.diff[0].attach[1].ip_address' + - 'result.diff[0].net_name == "ansible-net13"' + - 'result.diff[0].net_id == 7005' + - 'result.diff[0].vrf_name == "Tenant-1"' + + - name: TOR - Attach new TOR ports to already Attached network ports + cisco.dcnm.dcnm_network: &conf3 + fabric: "{{ test_fabric }}" + state: merged + config: + - net_name: ansible-net13 + vrf_name: Tenant-1 + net_id: 7005 + net_template: Default_Network_Universal + net_extension_template: Default_Network_Extension_Universal + vlan_id: 1500 + gw_ip_subnet: '192.168.30.1/24' + attach: + - ip_address: "{{ ansible_switch1 }}" + ports: ["{{ ansible_sw1_int1 }}", "{{ ansible_sw1_int2 }}"] + tor_ports: + - ip_address: "{{ ansible_tor_switch1 }}" + ports: ["{{ ansible_tor_int3 }}"] + - ip_address: "{{ ansible_switch2 }}" + ports: ["{{ ansible_sw2_int5 }}", "{{ ansible_sw2_int6 }}"] + deploy: True + register: result + + - name: Query fabric state until networkStatus transitions to DEPLOYED state + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: query + register: query_result + until: + - "query_result.response[0].parent.networkStatus is search('DEPLOYED|PENDING')" + retries: 30 + delay: 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.diff[0].attach[0].deploy == true' + - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ ansible_tor_int3 }}" in result.diff[0].attach[0].tor_ports' + - 'result.diff[0].net_name == "ansible-net13"' + + - name: TOR - conf3 - Idempotence + cisco.dcnm.dcnm_network: *conf3 + register: result + + - assert: + that: + - 'result.changed == false' + - 'result.response|length == 0' + + - name: TOR - Replace new TOR ports to already Attached network ports + cisco.dcnm.dcnm_network: &conf4 + fabric: "{{ test_fabric }}" + state: replaced + config: + - net_name: ansible-net13 + vrf_name: Tenant-1 + net_id: 7005 + net_template: Default_Network_Universal + net_extension_template: Default_Network_Extension_Universal + vlan_id: 1500 + gw_ip_subnet: '192.168.30.1/24' + attach: + - ip_address: "{{ ansible_switch1 }}" + ports: ["{{ ansible_sw1_int1 }}", "{{ ansible_sw1_int2 }}"] + tor_ports: + - ip_address: "{{ ansible_tor_switch1 }}" + ports: ["{{ ansible_tor_int1 }}", "{{ ansible_tor_int2 }}"] + - ip_address: "{{ ansible_switch2 }}" + ports: ["{{ ansible_sw2_int5 }}", "{{ ansible_sw2_int6 }}"] + deploy: True + register: result + + - name: Query fabric state until networkStatus transitions to DEPLOYED state + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: query + register: query_result + until: + - "query_result.response[0].parent.networkStatus is search('DEPLOYED|PENDING')" + retries: 30 + delay: 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.diff[0].attach[0].deploy == true' + - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ ansible_tor_int1 }}" in result.diff[0].attach[0].tor_ports' + - '"{{ ansible_tor_int2 }}" in result.diff[0].attach[0].tor_ports' + - 'result.diff[0].net_name == "ansible-net13"' + + - name: TOR - conf4 - Idempotence + cisco.dcnm.dcnm_network: *conf4 + register: result + + - assert: + that: + - 'result.changed == false' + - 'result.response|length == 0' + + - name: TOR - Override new TOR ports to already Attached network ports + cisco.dcnm.dcnm_network: &conf5 + fabric: "{{ test_fabric }}" + state: overridden + config: + - net_name: ansible-net13 + vrf_name: Tenant-1 + net_id: 7005 + net_template: Default_Network_Universal + net_extension_template: Default_Network_Extension_Universal + vlan_id: 1500 + gw_ip_subnet: '192.168.30.1/24' + attach: + - ip_address: "{{ ansible_switch1 }}" + ports: ["{{ ansible_sw1_int1 }}", "{{ ansible_sw1_int2 }}"] + tor_ports: + - ip_address: "{{ ansible_tor_switch1 }}" + ports: ["{{ ansible_tor_int3 }}", "{{ ansible_tor_int4 }}"] + deploy: True + register: result + + - name: Query fabric state until networkStatus transitions to DEPLOYED state + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: query + register: query_result + until: + - "query_result.response[0].parent.networkStatus is search('DEPLOYED|PENDING')" + retries: 30 + delay: 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.diff[0].attach[0].deploy == true' + - '"{{ ansible_switch1 }}" in result.diff[0].attach[0].ip_address' + - '"{{ ansible_tor_int3 }}" in result.diff[0].attach[0].tor_ports' + - '"{{ ansible_tor_int4 }}" in result.diff[0].attach[0].tor_ports' + - 'result.diff[0].net_name == "ansible-net13"' + + - name: TOR - conf5 - Idempotence + cisco.dcnm.dcnm_network: *conf5 + register: result + + - assert: + that: + - 'result.changed == false' + - 'result.response|length == 0' + +############################################## +## CLEAN-UP ## +############################################## + + - name: MERGED - setup - remove any networks + cisco.dcnm.dcnm_network: + fabric: "{{ test_fabric }}" + state: deleted + when: test_tor_pair is defined diff --git a/tests/unit/modules/dcnm/fixtures/dcnm_network.json b/tests/unit/modules/dcnm/fixtures/dcnm_network.json index df426a4da..e212c90a9 100644 --- a/tests/unit/modules/dcnm/fixtures/dcnm_network.json +++ b/tests/unit/modules/dcnm/fixtures/dcnm_network.json @@ -1,7 +1,9 @@ { "mock_ip_sn" : { "10.10.10.217": "9NN7E41N16A", - "10.10.10.218": "9YO9A29F27U", + "10.10.10.218": "9YO9A29F27U", + "10.10.10.219": "9YO9A29F28C", + "10.10.10.220": "9YO9A29F29D", "10.10.10.226": "XYZKSJHSMK3", "10.10.10.227": "XYZKSJHSMK4", "10.10.10.228": "XYZKSJHSMK5" @@ -17,7 +19,7 @@ "gw_ip_subnet": "192.168.30.1/24", "attach": [ { - "ip_address": "10.10.10.217", + "ip_address": "10.10.10.217", "ports": ["Ethernet1/13", "Ethernet1/14"], "deploy": true }, @@ -30,6 +32,68 @@ "deploy": true } ], + "playbook_tor_config" : [ + { + "net_name": "test_network", + "vrf_name": "ansible-vrf-int1", + "net_id": "9008011", + "net_template": "Default_Network_Universal", + "net_extension_template": "Default_Network_Extension_Universal", + "vlan_id": "202", + "gw_ip_subnet": "192.168.30.1/24", + "attach": [ + { + "ip_address": "10.10.10.217", + "ports": ["Ethernet1/13", "Ethernet1/14"], + "tor_ports": [ + { + "ip_address": "10.10.10.219", + "ports": ["Ethernet1/13", "Ethernet1/14"] + } + ] + }, + { + "ip_address": "10.10.10.218", + "ports": ["Ethernet1/13", "Ethernet1/14"], + "tor_ports": [ + { + "ip_address": "10.10.10.220", + "ports": ["Ethernet1/15", "Ethernet1/16"] + } + ] + } + ], + "deploy": true + } + ], + "playbook_tor_roleerr_config" : [ + { + "net_name": "test_network", + "vrf_name": "ansible-vrf-int1", + "net_id": "9008011", + "net_template": "Default_Network_Universal", + "net_extension_template": "Default_Network_Extension_Universal", + "vlan_id": "202", + "gw_ip_subnet": "192.168.30.1/24", + "attach": [ + { + "ip_address": "10.10.10.228", + "ports": ["Ethernet1/13", "Ethernet1/14"], + "tor_ports": [ + { + "ip_address": "10.10.10.220", + "ports": ["Ethernet1/13", "Ethernet1/14"] + } + ] + }, + { + "ip_address": "10.10.10.218", + "ports": ["Ethernet1/13", "Ethernet1/14"] + } + ], + "deploy": true + } + ], "playbook_config_incorrect_netid" : [ { "net_name": "test_network", @@ -102,6 +166,42 @@ "deploy": true } ], + "playbook_tor_config_update" : [ + { + "net_name": "test_network", + "vrf_name": "ansible-vrf-int1", + "net_id": "9008011", + "net_template": "Default_Network_Universal", + "net_extension_template": "Default_Network_Extension_Universal", + "vlan_id": "203", + "gw_ip_subnet": "192.168.30.1/24", + "attach": [ + { + "ip_address": "10.10.10.218", + "ports": ["Ethernet1/13", "Ethernet1/14"], + "deploy": true, + "tor_ports": [ + { + "ip_address": "10.10.10.219", + "ports": ["Ethernet1/13", "Ethernet1/14"] + } + ] + }, + { + "ip_address": "10.10.10.217", + "ports": ["Ethernet1/13", "Ethernet1/14"], + "deploy": true, + "tor_ports": [ + { + "ip_address": "10.10.10.220", + "ports": ["Ethernet1/13", "Ethernet1/14"] + } + ] + } + ], + "deploy": true + } + ], "playbook_config_replace" : [ { "net_name": "test_network", @@ -335,6 +435,50 @@ } ] }, + "mock_net_attach_tor_object" : { + "MESSAGE": "OK", + "METHOD": "POST", + "RETURN_CODE": 200, + "DATA": [ + { + "networkName": "test_network", + "lanAttachList": [ + { + "networkName": "test_network", + "displayName": "test_network", + "switchName": "n9kv-218", + "switchRole": "leaf", + "fabricName": "test_net", + "lanAttachState": "DEPLOYED", + "isLanAttached": true, + "portNames": "dt-n9k2(Ethernet1/13,Ethernet1/14) dt-n9k6(Ethernet1/12)", + "switchSerialNo": "9YO9A29F27U", + "switchDbId": 4191270, + "ipAddress": "10.10.10.218", + "networkId": 9008011, + "vlanId": 202, + "interfaceGroups": null + }, + { + "networkName": "test_network", + "displayName": "test_network", + "switchName": "n9kv-217", + "switchRole": "leaf", + "fabricName": "test_net", + "lanAttachState": "DEPLOYED", + "isLanAttached": true, + "portNames": "dt-n9k1(Ethernet1/13,Ethernet1/14) dt-n9k7(Ethernet1/12)", + "switchSerialNo": "9NN7E41N16A", + "switchDbId": 4195850, + "ipAddress": "10.10.10.217", + "networkId": 9008011, + "vlanId": 202, + "interfaceGroups": null + } + ] + } + ] + }, "mock_net_attach_object_del_ready": { "ERROR": "", "RETURN_CODE": 200, @@ -468,6 +612,18 @@ "serialNumber": "9YO9A29F27U", "switchRole": "leaf" }, + "10.10.10.219":{ + "ipAddress": "10.10.10.219", + "logicalName": "dt-n9k6", + "serialNumber": "9YO9A29F28C", + "switchRole": "tor" + }, + "10.10.10.220":{ + "ipAddress": "10.10.10.220", + "logicalName": "dt-n9k7", + "serialNumber": "9YO9A29F29D", + "switchRole": "tor" + }, "10.10.10.226":{ "ipAddress": "10.10.10.226", "logicalName": "dt-n9k3", diff --git a/tests/unit/modules/dcnm/test_dcnm_network.py b/tests/unit/modules/dcnm/test_dcnm_network.py index 3b0bc2980..88d635a9e 100644 --- a/tests/unit/modules/dcnm/test_dcnm_network.py +++ b/tests/unit/modules/dcnm/test_dcnm_network.py @@ -47,6 +47,9 @@ class TestDcnmNetworkModule(TestDcnmModule): playbook_config_incorrect_vrf = test_data.get("playbook_config_incorrect_vrf") playbook_config_update = test_data.get("playbook_config_update") playbook_config_novlan = test_data.get("playbook_config_novlan") + playbook_tor_config = test_data.get("playbook_tor_config") + playbook_tor_roleerr_config = test_data.get("playbook_tor_roleerr_config") + playbook_tor_config_update = test_data.get("playbook_tor_config_update") playbook_config_replace = test_data.get("playbook_config_replace") playbook_config_replace_no_atch = test_data.get("playbook_config_replace_no_atch") @@ -79,6 +82,7 @@ def init_data(self): ) self.mock_net_query_object = copy.deepcopy(self.test_data.get("mock_net_query_object")) self.mock_vlan_get = copy.deepcopy(self.test_data.get("mock_vlan_get")) + self.mock_net_attach_tor_object = copy.deepcopy(self.test_data.get("mock_net_attach_tor_object")) def setUp(self): super(TestDcnmNetworkModule, self).setUp() @@ -370,6 +374,54 @@ def load_fixtures(self, response=None, device=""): self.mock_net_attach_object ] + elif "_merged_torport_new" in self._testMethodName: + self.init_data() + self.run_dcnm_send.side_effect = [ + self.mock_vrf_object, + self.blank_data, + self.blank_data, + self.attach_success_resp, + self.deploy_success_resp, + ] + + elif "_merged_torport_vererror" in self._testMethodName: + self.init_data() + + elif "_merged_torport_roleerror" in self._testMethodName: + self.init_data() + + elif "_merged_tor_with_update" in self._testMethodName: + self.init_data() + self.run_dcnm_get_url.side_effect = [self.mock_net_attach_tor_object] + self.run_dcnm_send.side_effect = [ + self.mock_vrf_object, + self.mock_net_object, + self.blank_data, + self.attach_success_resp, + self.deploy_success_resp, + ] + + elif "_replace_tor_ports" in self._testMethodName: + self.init_data() + self.run_dcnm_get_url.side_effect = [self.mock_net_attach_tor_object] + self.run_dcnm_send.side_effect = [ + self.mock_vrf_object, + self.mock_net_object, + self.blank_data, + self.attach_success_resp, + self.deploy_success_resp, + ] + + elif "_override_tor_ports" in self._testMethodName: + self.init_data() + self.run_dcnm_get_url.side_effect = [self.mock_net_attach_tor_object] + self.run_dcnm_send.side_effect = [ + self.mock_vrf_object, + self.mock_net_object, + self.blank_data, + self.attach_success_resp, + self.deploy_success_resp, + ] else: pass @@ -735,3 +787,107 @@ def test_dcnm_net_query_without_config(self): result.get("response")[0]["attach"][1]["vlan"], 202, ) + + def test_dcnm_net_merged_torport_new(self): + self.version = 12 + set_module_args( + dict(state="merged", fabric="test_network", config=self.playbook_tor_config) + ) + result = self.execute_module(changed=True, failed=False) + self.version = 11 + self.assertTrue(result.get("diff")[0]["attach"][0]["deploy"]) + self.assertTrue(result.get("diff")[0]["attach"][1]["deploy"]) + self.assertEqual( + result.get("diff")[0]["attach"][0]["ip_address"], "10.10.10.217" + ) + + def test_dcnm_net_merged_torport_vererror(self): + set_module_args( + dict(state="merged", fabric="test_network", config=self.playbook_tor_config) + ) + result = self.execute_module(changed=False, failed=True) + self.assertEqual( + result.get("msg"), + "Invalid parameters in playbook: tor_ports configurations are supported only on NDFC", + ) + + def test_dcnm_net_merged_torport_roleerror(self): + self.version = 12 + set_module_args( + dict(state="merged", fabric="test_network", config=self.playbook_tor_roleerr_config) + ) + result = self.execute_module(changed=False, failed=True) + self.version = 11 + self.assertEqual( + result.get("msg"), + "tor_ports for Networks cannot be attached to switch 10.10.10.228 with role border", + ) + + def test_dcnm_net_merged_tor_with_update(self): + self.version = 12 + set_module_args( + dict( + state="merged", fabric="test_network", config=self.playbook_tor_config_update + ) + ) + result = self.execute_module(changed=True, failed=False) + self.version = 11 + self.assertTrue(result.get("diff")[0]["attach"][0]["deploy"]) + self.assertTrue(result.get("diff")[0]["attach"][1]["deploy"]) + self.assertEqual( + result.get("diff")[0]["attach"][0]["ip_address"], "10.10.10.218" + ) + self.assertEqual( + result.get("diff")[0]["attach"][1]["ip_address"], "10.10.10.217" + ) + self.assertEqual(result.get("diff")[0]["vrf_name"], "ansible-vrf-int1") + + def test_dcnm_net_replace_tor_ports(self): + self.version = 12 + set_module_args( + dict( + state="replaced", fabric="test_network", config=self.playbook_tor_config_update + ) + ) + result = self.execute_module(changed=True, failed=False) + self.version = 11 + self.assertTrue(result.get("diff")[0]["attach"][0]["deploy"]) + self.assertTrue(result.get("diff")[0]["attach"][1]["deploy"]) + self.assertEqual( + result.get("diff")[0]["attach"][0]["ip_address"], "10.10.10.218" + ) + self.assertEqual( + result.get("diff")[0]["attach"][1]["ip_address"], "10.10.10.217" + ) + self.assertEqual( + result.get("diff")[0]["attach"][0]["tor_ports"], "dt-n9k6(Ethernet1/13,Ethernet1/14)" + ) + self.assertEqual( + result.get("diff")[0]["attach"][1]["tor_ports"], "dt-n9k7(Ethernet1/13,Ethernet1/14)" + ) + self.assertEqual(result.get("diff")[0]["vrf_name"], "ansible-vrf-int1") + + def test_dcnm_net_override_tor_ports(self): + self.version = 12 + set_module_args( + dict( + state="overridden", fabric="test_network", config=self.playbook_tor_config_update + ) + ) + result = self.execute_module(changed=True, failed=False) + self.version = 11 + self.assertTrue(result.get("diff")[0]["attach"][0]["deploy"]) + self.assertTrue(result.get("diff")[0]["attach"][1]["deploy"]) + self.assertEqual( + result.get("diff")[0]["attach"][0]["ip_address"], "10.10.10.218" + ) + self.assertEqual( + result.get("diff")[0]["attach"][1]["ip_address"], "10.10.10.217" + ) + self.assertEqual( + result.get("diff")[0]["attach"][0]["tor_ports"], "dt-n9k6(Ethernet1/13,Ethernet1/14)" + ) + self.assertEqual( + result.get("diff")[0]["attach"][1]["tor_ports"], "dt-n9k7(Ethernet1/13,Ethernet1/14)" + ) + self.assertEqual(result.get("diff")[0]["vrf_name"], "ansible-vrf-int1") From 9316e52ab621852229ff0ef3fe13b9bd0603709d Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Thu, 12 Oct 2023 18:43:39 +0530 Subject: [PATCH 8/9] Support attach network to paired TOR switch - replace fix --- plugins/modules/dcnm_network.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index 6e281853a..15385cd27 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -609,7 +609,7 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): want.update({"torPorts": torconfig}) # Update torports_configured to True. If there is no other config change for attach # We will still append this attach to attach_list as there is tor port change - if atch_tor_ports != h_tor_ports: + if sorted(atch_tor_ports) != sorted(h_tor_ports): torports_configured = True if not torports_present: @@ -623,11 +623,19 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False): del have["torports"] elif have.get("torports"): - # Dont update torports_configured to True. - # If at all there is any other config change, this attach to will be appended attach_list there - for tor_h in have.get("torports"): - torconfig = tor_h["switch"] + "(" + tor_h["torPorts"] + ")" - want.update({"torPorts": torconfig}) + if replace: + # There are tor ports configured, but it has to be removed as want tor ports are not present + # and state is replaced/overridden. Update torports_configured to True to remove tor ports + want.update({"torPorts": ""}) + torports_configured = True + + else: + # Dont update torports_configured to True. + # If at all there is any other config change, this attach to will be appended attach_list there + for tor_h in have.get("torports"): + torconfig = tor_h["switch"] + "(" + tor_h["torPorts"] + ")" + want.update({"torPorts": torconfig}) + del have["torports"] if want.get("torports"): From 6de2ce458ce3ec9a72e5dcb7bd7cd4778265fff4 Mon Sep 17 00:00:00 2001 From: Praveen Ramoorthy Date: Wed, 18 Oct 2023 19:15:36 +0530 Subject: [PATCH 9/9] Support attach network to paired TOR switch - Addressed review comments --- .../tests/dcnm/self-contained-tests/tor_ports_networks.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/tor_ports_networks.yaml b/tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/tor_ports_networks.yaml index e5ddfb905..648861cac 100644 --- a/tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/tor_ports_networks.yaml +++ b/tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/tor_ports_networks.yaml @@ -11,7 +11,7 @@ rest_path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ test_fabric }}" when: controller_version >= "12" - - name: SCALE - Verify if fabric is deployed. + - name: TOR - Verify if fabric is deployed. cisco.dcnm.dcnm_rest: method: GET path: "{{ rest_path }}" @@ -21,7 +21,7 @@ that: - 'result.response.DATA != None' - - name: SCALE - Clean up any existing networks + - name: TOR - Clean up any existing networks cisco.dcnm.dcnm_network: fabric: "{{ test_fabric }}" state: deleted @@ -359,7 +359,7 @@ ## CLEAN-UP ## ############################################## - - name: MERGED - setup - remove any networks + - name: TOR - setup - remove any networks cisco.dcnm.dcnm_network: fabric: "{{ test_fabric }}" state: deleted