Skip to content

Commit b2ae8f5

Browse files
jeestr4dtakishida
andauthored
feat: Support Floating L3Out in L3Out MTU check and add pytest (#272)
Add followings for `l3out_mtu_check()`: * pytest * floating L3Out checks * VLAN column --------- Co-authored-by: tkishida <tkishida@cisco.com>
1 parent a012491 commit b2ae8f5

6 files changed

Lines changed: 207 additions & 24 deletions

aci-preupgrade-validation-script.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from collections import defaultdict
2424
from datetime import datetime
2525
from argparse import ArgumentParser
26+
from itertools import chain
2627
import functools
2728
import shutil
2829
import warnings
@@ -1735,36 +1736,60 @@ def switch_bootflash_usage_check(tversion, **kwargs):
17351736
def l3out_mtu_check(**kwargs):
17361737
result = MANUAL
17371738
msg = ""
1738-
headers = ["Tenant", "L3Out", "Node Profile", "Logical Interface Profile",
1739-
"Pod", "Node", "Interface", "Type", "IP Address", "MTU"]
1739+
headers = ["Tenant", "L3Out", "Node Profile", "Interface Profile",
1740+
"Pod", "Node", "Interface", "Type", "VLAN", "IP Address", "MTU"]
17401741
data = []
17411742
unformatted_headers = ['L3 DN', "Type", "IP Address", "MTU"]
17421743
unformatted_data = []
17431744
recommended_action = 'Verify that these MTUs match with connected devices'
17441745
doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#l3out-mtu"
17451746

1746-
dn_regex = r'tn-(?P<tenant>[^/]+)/out-(?P<l3out>[^/]+)/lnodep-(?P<lnodep>[^/]+)/lifp-(?P<lifp>[^/]+)/rspathL3OutAtt-\[topology/pod-(?P<pod>[^/]+)/.*paths-(?P<nodes>\d{3,4}|\d{3,4}-\d{3,4})/pathep-\[(?P<int>.+)\]\]'
1747-
response_json = icurl('class', 'l3extRsPathL3OutAtt.json')
1748-
if response_json:
1749-
l2Pols = icurl('mo', 'uni/fabric/l2pol-default.json')
1750-
fabricMtu = l2Pols[0]['l2InstPol']['attributes']['fabricMtu']
1751-
for l3extRsPathL3OutAtt in response_json:
1752-
mtu = l3extRsPathL3OutAtt['l3extRsPathL3OutAtt']['attributes']['mtu']
1753-
iftype = l3extRsPathL3OutAtt['l3extRsPathL3OutAtt']['attributes']['ifInstT']
1754-
addr = l3extRsPathL3OutAtt['l3extRsPathL3OutAtt']['attributes']['addr']
1755-
1756-
if mtu == 'inherit':
1757-
mtu += " (%s)" % fabricMtu
1758-
1759-
dn = re.search(dn_regex, l3extRsPathL3OutAtt['l3extRsPathL3OutAtt']['attributes']['dn'])
1760-
1761-
if dn:
1762-
data.append([dn.group("tenant"), dn.group("l3out"), dn.group("lnodep"),
1763-
dn.group("lifp"), dn.group("pod"), dn.group("nodes"),
1764-
dn.group("int"), iftype, addr, mtu])
1765-
else:
1766-
unformatted_data.append(
1767-
[l3extRsPathL3OutAtt['l3extRsPathL3OutAtt']['attributes']['dn'], iftype, addr, mtu])
1747+
fabricMtu = None
1748+
regex_prefix = r'tn-(?P<tenant>[^/]+)/out-(?P<l3out>[^/]+)/lnodep-(?P<lnodep>[^/]+)/lifp-(?P<lifp>[^/]+)'
1749+
path_dn_regex = regex_prefix + r'/rspathL3OutAtt-\[topology/pod-(?P<pod>[^/]+)/.*paths-(?P<node>\d{3,4}|\d{3,4}-\d{3,4})/pathep-\[(?P<int>.+)\]\]'
1750+
vlif_dn_regex = regex_prefix + r'/vlifp-\[topology/pod-(?P<pod>[^/]+)/node-(?P<node>\d{3,4})\]-\[vlan-(\d{1,4})\]'
1751+
l3extPaths = icurl('class', 'l3extRsPathL3OutAtt.json') # Regular L3Out
1752+
try:
1753+
l3extVLIfPs = icurl('class', 'l3extVirtualLIfP.json') # Floating L3Out
1754+
except OldVerClassNotFound:
1755+
l3extVLIfPs = [] # Pre 4.2 did not have this class
1756+
for mo in chain(l3extPaths, l3extVLIfPs):
1757+
if fabricMtu is None:
1758+
l2Pols = icurl('mo', 'uni/fabric/l2pol-default.json')
1759+
fabricMtu = l2Pols[0]['l2InstPol']['attributes']['fabricMtu']
1760+
1761+
is_floating = True if mo.get('l3extVirtualLIfP') else False
1762+
1763+
mo_class = 'l3extVirtualLIfP' if is_floating else 'l3extRsPathL3OutAtt'
1764+
mtu = mo[mo_class]['attributes']['mtu']
1765+
addr = mo[mo_class]['attributes']['addr']
1766+
vlan = mo[mo_class]['attributes']['encap']
1767+
iftype = mo[mo_class]['attributes']['ifInstT']
1768+
# Differentiate between regular and floating SVI. Both use ext-svi in the object.
1769+
if is_floating:
1770+
iftype = "floating svi"
1771+
1772+
if mtu == 'inherit':
1773+
mtu += " (%s)" % fabricMtu
1774+
1775+
dn_regex = vlif_dn_regex if is_floating else path_dn_regex
1776+
dn = re.search(dn_regex, mo[mo_class]['attributes']['dn'])
1777+
if dn:
1778+
data.append([
1779+
dn.group("tenant"),
1780+
dn.group("l3out"),
1781+
dn.group("lnodep"),
1782+
dn.group("lifp"),
1783+
dn.group("pod"),
1784+
dn.group("node"),
1785+
dn.group("int") if not is_floating else '---',
1786+
iftype,
1787+
vlan,
1788+
addr,
1789+
mtu,
1790+
])
1791+
else:
1792+
unformatted_data.append([mo[mo_class]['attributes']['dn'], iftype, addr, mtu])
17681793

17691794
if not data and not unformatted_data:
17701795
result = NA
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[
2+
{
3+
"l2InstPol": {
4+
"attributes": {
5+
"annotation": "",
6+
"childAction": "",
7+
"descr": "",
8+
"dn": "uni/fabric/l2pol-default",
9+
"extMngdBy": "",
10+
"fabricMtu": "9000",
11+
"lcOwn": "local",
12+
"managementMtu": "9000",
13+
"modTs": "2025-04-28T23:16:48.741+00:00",
14+
"name": "default",
15+
"nameAlias": "",
16+
"ownerKey": "",
17+
"ownerTag": "",
18+
"status": "",
19+
"uid": "0",
20+
"userdom": "all"
21+
}
22+
}
23+
}
24+
]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[
2+
{
3+
"l3extRsPathL3OutAtt": {
4+
"attributes": {
5+
"addr": "122.1.1.1/30",
6+
"dn": "uni/tn-infra/out-IPNL3Out/lnodep-LNodeP_2001/lifp-LIfP_2001/rspathL3OutAtt-[topology/pod-2/paths-2001/pathep-[eth1/26]]",
7+
"encap": "vlan-4",
8+
"ifInstT": "sub-interface",
9+
"ipv6Dad": "enabled",
10+
"isMultiPodDirect": "no",
11+
"lcOwn": "local",
12+
"llAddr": "::",
13+
"mac": "00:22:BD:F8:19:FF",
14+
"mtu": "inherit",
15+
"tCl": "fabricPathEp",
16+
"tDn": "topology/pod-2/paths-2001/pathep-[eth1/26]",
17+
"tType": "mo",
18+
"targetDscp": "unspecified",
19+
"uid": "15374",
20+
"userdom": "all"
21+
}
22+
}
23+
},
24+
{
25+
"l3extRsPathL3OutAtt": {
26+
"attributes": {
27+
"addr": "102.1.1.1/30",
28+
"dn": "uni/tn-infra/out-IPNL3Out/lnodep-LNodeP_1001/lifp-LIfP_1001/rspathL3OutAtt-[topology/pod-1/paths-1001/pathep-[eth1/6]]",
29+
"encap": "vlan-4",
30+
"ifInstT": "sub-interface",
31+
"mtu": "9150",
32+
"tCl": "fabricPathEp",
33+
"tDn": "topology/pod-1/paths-1001/pathep-[eth1/6]" }
34+
}
35+
}
36+
]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[
2+
{
3+
"l3extVirtualLIfP": {
4+
"attributes": {
5+
"addr": "172.16.20.6/29",
6+
"dn": "uni/tn-vavictor_TenantA/out-Static_FloatL3Out/lnodep-Static_FloatL3Out_nodeProfile/lifp-Static_FloatL3Out_interfaceProfile/vlifp-[topology/pod-1/node-102]-[vlan-46]",
7+
"encap": "vlan-46",
8+
"encapScope": "local",
9+
"extMngdBy": "",
10+
"ifInstT": "ext-svi",
11+
"ipv6Dad": "enabled",
12+
"lcOwn": "local",
13+
"llAddr": "::",
14+
"mac": "00:22:BD:F8:19:FF",
15+
"mtu": "9000"
16+
}
17+
}
18+
},
19+
{
20+
"l3extVirtualLIfP": {
21+
"attributes": {
22+
"addr": "10.0.1.101/24",
23+
"dn": "uni/tn-vmenchac_test/out-Floating_L3out/lnodep-Floating_L3out_nodeProfile/lifp-Floating_L3out_interfaceProfile/vlifp-[topology/pod-1/node-101]-[vlan-101]",
24+
"encap": "vlan-101",
25+
"encapScope": "local",
26+
"extMngdBy": "",
27+
"ifInstT": "ext-svi",
28+
"ipv6Dad": "enabled",
29+
"lcOwn": "local",
30+
"llAddr": "::",
31+
"mac": "00:22:BD:F8:19:FF",
32+
"mtu": "1500"
33+
}
34+
}
35+
}
36+
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"error": {
4+
"attributes": {
5+
"code": "400",
6+
"text": "Request failed, unresolved class for l3extVirtualLIfP"
7+
}
8+
}
9+
}
10+
]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
import pytest
3+
import logging
4+
import importlib
5+
from helpers.utils import read_data
6+
7+
script = importlib.import_module("aci-preupgrade-validation-script")
8+
9+
log = logging.getLogger(__name__)
10+
dir = os.path.dirname(os.path.abspath(__file__))
11+
12+
13+
# icurl queries
14+
regular_api = "l3extRsPathL3OutAtt.json"
15+
floating_api = "l3extVirtualLIfP.json"
16+
l2Pols = "uni/fabric/l2pol-default.json"
17+
18+
19+
@pytest.mark.parametrize(
20+
"icurl_outputs, expected_result",
21+
[
22+
# No Response from neither l3extRsPathL3OutAtt or l3extVirtualLIfP, result is NA
23+
({regular_api: [], floating_api: [], l2Pols: []}, script.NA),
24+
# No Response from l3extRsPathL3OutAtt, Version too old for l3extVirtualLIfP, result is NA
25+
({regular_api: [], floating_api: read_data(dir, "l3extVirtualLIfP_unresolved.json"), l2Pols: []}, script.NA),
26+
# No Response from l3extVirtualLIfP, Reponse from l3extRsPathL3OutAtt result is MANUAL
27+
({regular_api: read_data(dir, "l3extRsPathL3OutAtt.json"), floating_api: [], l2Pols: read_data(dir, "l2pol-default.json")}, script.MANUAL),
28+
# No Response from l3extRsPathL3OutAtt, Reponse from l3extVirtualLIfP result is MANUAL
29+
({regular_api: [], floating_api: read_data(dir, "l3extVirtualLIfP.json"), l2Pols: read_data(dir, "l2pol-default.json")}, script.MANUAL),
30+
# Response from l3extRsPathL3OutAtt and l3extVirtualLIfP result is MANUAL
31+
(
32+
{
33+
regular_api: read_data(dir, "l3extRsPathL3OutAtt.json"),
34+
floating_api: read_data(dir, "l3extVirtualLIfP.json"),
35+
l2Pols: read_data(dir, "l2pol-default.json"),
36+
},
37+
script.MANUAL,
38+
),
39+
# Response from l3extRsPathL3OutAtt, Version too old for l3extVirtualLIfP, result is MANUAL
40+
(
41+
{
42+
regular_api: read_data(dir, "l3extRsPathL3OutAtt.json"),
43+
floating_api: read_data(dir, "l3extVirtualLIfP_unresolved.json"),
44+
l2Pols: read_data(dir, "l2pol-default.json"),
45+
},
46+
script.MANUAL,
47+
),
48+
],
49+
)
50+
def test_logic(mock_icurl, expected_result):
51+
result = script.l3out_mtu_check(1, 1)
52+
assert result == expected_result

0 commit comments

Comments
 (0)