diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index ebfd23c..634b876 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1705,6 +1705,60 @@ def l3out_route_map_direction_check(index, total_checks, **kwargs): return result +def l3out_route_map_missing_target_check(index, total_checks, cversion, tversion, **kwargs): + """ Implementation change due to CSCwc11570 - 5.2.8/6.0.2 """ + title = 'L3Out Route Map Match Rule with missing-target' + result = FAIL_O + msg = '' + headers = ['Tenant', 'L3Out', 'Route Map', 'Context', 'Action', 'Match Rule'] + data = [] + recommended_action = 'The configured match rules do not exist. Update the route maps with existing match rules.' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#l3out-route-map-match-rule-with-missing-target' + print_title(title, index, total_checks) + + if not tversion: + print_result(title, MANUAL, "Target version not supplied. Skipping.") + return MANUAL + + def is_old(v): + return True if v.older_than("5.2(8a)") or v.simple_version == "6.0(1)" else False + + c_is_old = is_old(cversion) + t_is_old = is_old(tversion) + if (c_is_old and t_is_old) or (not c_is_old and not t_is_old): + print_result(title, NA) + return NA + + dn_regex = r'uni/tn-(?P[^/]+)/out-(?P[^/]+)/' + # Get a missing-target match rule in a route map with type `combinable` + api = 'rtctrlProfile.json' + api += '?query-target-filter=eq(rtctrlProfile.type,"combinable")' + api += '&rsp-subtree=full&rsp-subtree-filter=eq(rtctrlRsCtxPToSubjP.state,"missing-target")' + profiles = icurl('class', api) + for profile in profiles: + dn = re.search(dn_regex, profile['rtctrlProfile']['attributes']['dn']) + for ctxP in profile['rtctrlProfile'].get('children', []): + if not ctxP.get('rtctrlCtxP'): + continue + for rsCtxPToSubjP in ctxP['rtctrlCtxP'].get('children', []): + if ( + rsCtxPToSubjP.get('rtctrlRsCtxPToSubjP') + and rsCtxPToSubjP['rtctrlRsCtxPToSubjP']['attributes']['state'] == 'missing-target' + ): + data.append([ + dn.group('tenant'), + dn.group('l3out'), + profile['rtctrlProfile']['attributes']['name'], + ctxP['rtctrlCtxP']['attributes']['name'], + ctxP['rtctrlCtxP']['attributes']['action'], + rsCtxPToSubjP['rtctrlRsCtxPToSubjP']['attributes']['tnRtctrlSubjPName'], + ]) + if not data: + result = PASS + print_result(title, result, msg, headers, data, recommended_action=recommended_action, doc_url=doc_url) + return result + + def bgp_peer_loopback_check(index, total_checks, **kwargs): """ Implementation change due to CSCvm28482 - 4.1(2) """ title = 'BGP Peer Profile at node level without Loopback' @@ -2916,6 +2970,7 @@ def access_untagged_check(index, total_checks, **kwargs): l3out_mtu_check, bgp_peer_loopback_check, l3out_route_map_direction_check, + l3out_route_map_missing_target_check, intersight_upgrade_status_check, isis_redis_metric_mpod_msite_check, bgp_golf_route_target_type_check, diff --git a/docs/docs/validations.md b/docs/docs/validations.md index d4afea1..76d1bf1 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -101,11 +101,12 @@ Items | Faults | This Script [L3Out MTU][c4] | :white_check_mark: | :no_entry_sign: | :white_check_mark: [BGP Peer Profile at node level without Loopback][c5] | :white_check_mark: | :no_entry_sign: | :white_check_mark: [L3Out Route Map import/export direction][c6] | :white_check_mark: | :no_entry_sign: | :white_check_mark: -[ISIS Redistribution Metric for MPod/Msite][c7] | :white_check_mark: | :no_entry_sign: | :white_check_mark: -[BGP Route-target Type for GOLF over L2EVPN][c8] | :white_check_mark: | :no_entry_sign: | :white_check_mark: -[APIC Container Bridge IP Overlap with APIC TEP][c9] | :white_check_mark: | :no_entry_sign: | :no_entry_sign: -[Per-Leaf Fabric Uplink Scale Validation][c10] | :white_check_mark: | :no_entry_sign: | :no_entry_sign: -[OoB Mgmt Security][c11] | :white_check_mark: | :no_entry_sign: | :no_entry_sign: +[L3Out Route Map Match Rule with missing-target][c7] | :white_check_mark: | :no_entry_sign: | :white_check_mark: +[ISIS Redistribution Metric for MPod/Msite][c8] | :white_check_mark: | :no_entry_sign: | :white_check_mark: +[BGP Route-target Type for GOLF over L2EVPN][c9] | :white_check_mark: | :no_entry_sign: | :white_check_mark: +[APIC Container Bridge IP Overlap with APIC TEP][c10] | :white_check_mark: | :no_entry_sign: | :no_entry_sign: +[Per-Leaf Fabric Uplink Scale Validation][c11] | :white_check_mark: | :no_entry_sign: | :no_entry_sign: +[OoB Mgmt Security][c12] | :white_check_mark: | :no_entry_sign: | :no_entry_sign: [c1]: #vpc-paired-leaf-switches [c2]: #overlapping-vlan-pool @@ -113,11 +114,12 @@ Items | Faults | This Script [c4]: #l3out-mtu [c5]: #bgp-peer-profile-at-node-level-without-loopback [c6]: #l3out-route-map-importexport-direction -[c7]: #isis-redistribution-metric-for-mpodmsite -[c8]: #bgp-route-target-type-for-golf-over-l2evpn -[c9]: #apic-container-bridge-ip-overlap-with-apic-tep -[c10]: #fabric-uplink-scale-cannot-exceed-56-uplinks-per-leaf -[c11]: #oob-mgmt-security +[c7]: #l3out-route-map-match-rule-with-missing-target +[c8]: #isis-redistribution-metric-for-mpodmsite +[c9]: #bgp-route-target-type-for-golf-over-l2evpn +[c10]: #apic-container-bridge-ip-overlap-with-apic-tep +[c11]: #fabric-uplink-scale-cannot-exceed-56-uplinks-per-leaf +[c12]: #oob-mgmt-security ### Defect Condition Checks @@ -1299,7 +1301,7 @@ Due to this change, when upgrading from an older version to release 4.1(2) or la When multiple interfaces in the same node profile need to establish a BGP peer with the same peer IP, you might attempt to configure a **BGP Peer Connectivity Profile** at a node profile without a loopback so that the same BGP Peer configuration is applied, as a fallback due to the missing loopback, to each interface in the same node profile. This is because a **BGP Peer Connectivity Profile** with the same peer IP address could not be configured at multiple interface profiles within the same node profile. This limitation was loosened based on CSCvw88636 on 4.2(7f). Until then, for this specific requirement, you need to configure a node interface per interface profile and configure the **BGP Peer Connectivity Profile** at each interface profile in a different node profile. -### L3Out Route Map import/export direction +### L3Out Route Map import/export direction Prior to upgrading to release 4.1(1) or later, you must ensure that the route map (route profile) configuration is correct. @@ -1328,6 +1330,37 @@ Although this method is not the most common or recommended one among many ways t Also note that any import direction route maps only take effect when **Route Control Enforcement** is enabled for import under `Tenant > Networking > L3Out > Main profile`. Otherwise, everything is imported (learned) by default. +### L3Out Route Map Match Rule with missing-target + +Prior to upgrading to release 5.2(8), 6.0(2) or later, you must ensure that the route map (route profile) with type **Match Prefix AND Routing Policy** (type `combinable` in the object `rtctrlProfile`) does not contain a match rule with the `missing-target` state. + +Each context in an L3Out route-map with type **Match Prefix AND Routing Policy** creates a route-map sequence by combining multiple match conditions shown below: + +* Match rules referenced by the route-map context +* L3Out subnets to which the route-map is associated directly or through an L3Out EPG. +* BD subnets in a BD to which the route-map is associated + +!!! note + Match rule references in a route-map context is located at the following path in the APIC GUI: + + * `Tenants > Networking > L3Outs > (your L3Out) > Route map for import and export route control > Contexts > Match Rules` + + Whereas the match rule itself is located at the following path in the APIC GUI: + + * `Tanants > Policies > Protocol > Match Rules` + +However, prior to the fix of CSCwc11570, when the state of the match rule reference in the route map was `missing-target`, that is when the target match rule didn't exist due to a mis-configuration, no route-map sequence was created even when there were other match conditions such as L3Out subnets or BD subnets. + +After the fix of CSCwc11570, a route-map sequence is created correctly based on other available match conditions (i.e. L3Out/BD subnets) even when the state of the match rule reference is `missing-target`. + +Because of this, if users have route maps with type **Match Prefix AND Routing Policy** and a match rule reference that resulted in `missing-target` prior to an upgrade, route-map sequences that weren't deployed prior to the upgrade may be deployed after the upgrade. This may change how the routes are exported(advertised) or imported(learned) through an L3Out after the upgrade. + +To avoid an unexpected behavior change after an upgrade, if the route map context has a match rule reference with `missing-target`, you should either remove the match rule reference or update it such that it points to an existing match rule. + +!!! Tip + When a match rule reference with `missing-target` is present for a route map with type **Match Prefix AND Routing Policy** prior to the upgrade and prior to the fix of CSCwx11570, the corresponding route-map context is not functioning. However, the fact that it has not been functioning without any operational impact indicates that the route map context may not be needed in the first place. If you remove the match rule reference with `missing-target`, a new route-map sequence will be deployed based on other match conditions which may or may not be needed. Thus, it is important for you to check the original intent of the route-map context and decide how to resolve it. If the route-map context is not needed in the first place, you would need to remove the entire route-map context instead of just removing the match rule reference with `missing-target`. + + ### ISIS Redistribution Metric for MPod/Msite ISIS Redistribution Metric is used when a spine redistributes routes from another pod or site into local underlay network (ISIS). If this metric is not set to less than 63, traffic disruption may occur with an upgrade of spine swithces. diff --git a/tests/l3out_route_map_missing_target_check/rtctrlProfile_missing_target.json b/tests/l3out_route_map_missing_target_check/rtctrlProfile_missing_target.json new file mode 100644 index 0000000..97f7450 --- /dev/null +++ b/tests/l3out_route_map_missing_target_check/rtctrlProfile_missing_target.json @@ -0,0 +1,78 @@ +[ + { + "rtctrlProfile": { + "attributes": { + "annotation": "", + "autoContinue": "no", + "childAction": "", + "descr": "", + "direction": "import", + "dn": "uni/tn-TK/out-OSPF/prof-RMAP1", + "extMngdBy": "", + "lcOwn": "local", + "level": "0", + "modTs": "2024-04-17T13:01:33.705-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "RMAP1", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "status": "", + "type": "combinable", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlCtxP": { + "attributes": { + "action": "permit", + "annotation": "", + "childAction": "", + "descr": "", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2024-04-18T12:14:37.550-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "CTX1", + "nameAlias": "", + "order": "0", + "rn": "ctx-CTX1", + "status": "", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlRsCtxPToSubjP": { + "attributes": { + "annotation": "", + "childAction": "", + "extMngdBy": "", + "forceResolve": "yes", + "lcOwn": "local", + "modTs": "2024-04-18T12:16:27.269-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "rType": "mo", + "rn": "rsctxPToSubjP-Subj1", + "state": "missing-target", + "stateQual": "none", + "status": "", + "tCl": "rtctrlSubjP", + "tContextDn": "", + "tDn": "", + "tRn": "subj-Subj1", + "tType": "name", + "tnRtctrlSubjPName": "Subj1", + "uid": "15374", + "userdom": ":all:" + } + } + } + ] + } + } + ] + } + } +] diff --git a/tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_l3out_multiple_missing_target.json b/tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_l3out_multiple_missing_target.json new file mode 100644 index 0000000..bdcd3a3 --- /dev/null +++ b/tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_l3out_multiple_missing_target.json @@ -0,0 +1,154 @@ +[ + { + "rtctrlProfile": { + "attributes": { + "annotation": "", + "autoContinue": "no", + "childAction": "", + "descr": "", + "direction": "import", + "dn": "uni/tn-TK/out-OSPF/prof-RMAP1", + "extMngdBy": "", + "lcOwn": "local", + "level": "0", + "modTs": "2024-04-17T13:01:33.705-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "RMAP1", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "status": "", + "type": "combinable", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlCtxP": { + "attributes": { + "action": "permit", + "annotation": "", + "childAction": "", + "descr": "", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2024-04-18T12:14:37.550-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "CTX1", + "nameAlias": "", + "order": "0", + "rn": "ctx-CTX1", + "status": "", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlRsCtxPToSubjP": { + "attributes": { + "annotation": "", + "childAction": "", + "extMngdBy": "", + "forceResolve": "yes", + "lcOwn": "local", + "modTs": "2024-04-18T14:44:30.311-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "rType": "mo", + "rn": "rsctxPToSubjP-Subj1", + "state": "missing-target", + "stateQual": "none", + "status": "", + "tCl": "rtctrlSubjP", + "tContextDn": "", + "tDn": "", + "tRn": "subj-Subj1", + "tType": "name", + "tnRtctrlSubjPName": "Subj1", + "uid": "15374", + "userdom": ":all:" + } + } + } + ] + } + } + ] + } + }, + { + "rtctrlProfile": { + "attributes": { + "annotation": "", + "autoContinue": "no", + "childAction": "", + "descr": "", + "direction": "import", + "dn": "uni/tn-TK/out-BGP/prof-RMAP2", + "extMngdBy": "", + "lcOwn": "local", + "level": "0", + "modTs": "2024-04-18T14:43:52.378-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "RMAP2", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "status": "", + "type": "combinable", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlCtxP": { + "attributes": { + "action": "deny", + "annotation": "", + "childAction": "", + "descr": "", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2024-04-18T14:43:52.378-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "CTX0", + "nameAlias": "", + "order": "0", + "rn": "ctx-CTX0", + "status": "", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlRsCtxPToSubjP": { + "attributes": { + "annotation": "", + "childAction": "", + "extMngdBy": "", + "forceResolve": "yes", + "lcOwn": "local", + "modTs": "2024-04-18T14:44:30.311-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "rType": "mo", + "rn": "rsctxPToSubjP-Subj0", + "state": "missing-target", + "stateQual": "none", + "status": "", + "tCl": "rtctrlSubjP", + "tContextDn": "", + "tDn": "", + "tRn": "subj-Subj0", + "tType": "name", + "tnRtctrlSubjPName": "Subj0", + "uid": "15374", + "userdom": ":all:" + } + } + } + ] + } + } + ] + } + } +] diff --git a/tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_missing_target.json b/tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_missing_target.json new file mode 100644 index 0000000..eb6a810 --- /dev/null +++ b/tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_missing_target.json @@ -0,0 +1,127 @@ +[ + { + "rtctrlProfile": { + "attributes": { + "annotation": "", + "autoContinue": "no", + "childAction": "", + "descr": "", + "direction": "import", + "dn": "uni/tn-TK/out-OSPF/prof-RMAP1", + "extMngdBy": "", + "lcOwn": "local", + "level": "0", + "modTs": "2024-04-17T13:01:33.705-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "RMAP1", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "status": "", + "type": "combinable", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlCtxP": { + "attributes": { + "action": "permit", + "annotation": "", + "childAction": "", + "descr": "", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2024-04-18T12:46:40.464-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "CTX2", + "nameAlias": "", + "order": "1", + "rn": "ctx-CTX2", + "status": "", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlRsCtxPToSubjP": { + "attributes": { + "annotation": "", + "childAction": "", + "extMngdBy": "", + "forceResolve": "yes", + "lcOwn": "local", + "modTs": "2024-04-18T14:38:58.531-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "rType": "mo", + "rn": "rsctxPToSubjP-Subj2", + "state": "missing-target", + "stateQual": "none", + "status": "", + "tCl": "rtctrlSubjP", + "tContextDn": "", + "tDn": "", + "tRn": "subj-Subj2", + "tType": "name", + "tnRtctrlSubjPName": "Subj2", + "uid": "15374", + "userdom": ":all:" + } + } + } + ] + } + }, + { + "rtctrlCtxP": { + "attributes": { + "action": "permit", + "annotation": "", + "childAction": "", + "descr": "", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2024-04-18T12:14:37.550-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "CTX1", + "nameAlias": "", + "order": "0", + "rn": "ctx-CTX1", + "status": "", + "uid": "15374", + "userdom": ":" + }, + "children": [ + { + "rtctrlRsCtxPToSubjP": { + "attributes": { + "annotation": "", + "childAction": "", + "extMngdBy": "", + "forceResolve": "yes", + "lcOwn": "local", + "modTs": "2024-04-18T12:16:27.269-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "rType": "mo", + "rn": "rsctxPToSubjP-Subj1", + "state": "missing-target", + "stateQual": "none", + "status": "", + "tCl": "rtctrlSubjP", + "tContextDn": "", + "tDn": "", + "tRn": "subj-Subj1", + "tType": "name", + "tnRtctrlSubjPName": "Subj1", + "uid": "15374", + "userdom": ":all:" + } + } + } + ] + } + } + ] + } + } +] diff --git a/tests/l3out_route_map_missing_target_check/rtctrlProfile_no_missing_target.json b/tests/l3out_route_map_missing_target_check/rtctrlProfile_no_missing_target.json new file mode 100644 index 0000000..f144d62 --- /dev/null +++ b/tests/l3out_route_map_missing_target_check/rtctrlProfile_no_missing_target.json @@ -0,0 +1,27 @@ +[ + { + "rtctrlProfile": { + "attributes": { + "annotation": "", + "autoContinue": "no", + "childAction": "", + "descr": "", + "direction": "import", + "dn": "uni/tn-TK/out-OSPF/prof-RMAP1", + "extMngdBy": "", + "lcOwn": "local", + "level": "0", + "modTs": "2024-04-17T13:01:33.705-07:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "RMAP1", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "status": "", + "type": "combinable", + "uid": "15374", + "userdom": ":" + } + } + } +] diff --git a/tests/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py b/tests/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py new file mode 100644 index 0000000..045d9aa --- /dev/null +++ b/tests/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py @@ -0,0 +1,68 @@ +import os +import pytest +import logging +import importlib +from helpers.utils import read_data + +script = importlib.import_module("aci-preupgrade-validation-script") + +log = logging.getLogger(__name__) +dir = os.path.dirname(os.path.abspath(__file__)) + + +# icurl queries +profiles = 'rtctrlProfile.json' +profiles += '?query-target-filter=eq(rtctrlProfile.type,"combinable")' +profiles += '&rsp-subtree=full&rsp-subtree-filter=eq(rtctrlRsCtxPToSubjP.state,"missing-target")' + + +@pytest.mark.parametrize( + "icurl_outputs, cversion, tversion, expected_result", + [ + # Version not affected + ( + {profiles: read_data(dir, "rtctrlProfile_missing_target.json")}, + "5.2(8f)", + "5.3(2d)", + script.NA, + ), + # Version not affected + ( + {profiles: read_data(dir, "rtctrlProfile_missing_target.json")}, + "6.0(2h)", + "6.0(5h)", + script.NA, + ), + # Affected version, no missing_target + ( + {profiles: read_data(dir, "rtctrlProfile_no_missing_target.json")}, + "4.2(7s)", + "5.2(8f)", + script.PASS, + ), + # Affected version, one missing_target + ( + {profiles: read_data(dir, "rtctrlProfile_missing_target.json")}, + "4.2(7s)", + "5.2(8f)", + script.FAIL_O, + ), + # Affected version, multiple missing_target in the same route map + ( + {profiles: read_data(dir, "rtctrlProfile_multiple_missing_target.json")}, + "4.2(7s)", + "5.2(8f)", + script.FAIL_O, + ), + # Affected version, multiple missing_target in the different L3Out/route map + ( + {profiles: read_data(dir, "rtctrlProfile_multiple_l3out_multiple_missing_target.json")}, + "4.2(7s)", + "5.2(8f)", + script.FAIL_O, + ), + ], +) +def test_logic(mock_icurl, cversion, tversion, expected_result): + result = script.l3out_route_map_missing_target_check(1, 1, script.AciVersion(cversion), script.AciVersion(tversion)) + assert result == expected_result