Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions aci-preupgrade-validation-script.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@
("6.1(5)", "apicl4"): CIMC_RELEASE_NOTE_SUPPORT_615_M6,
("6.1(5)", "apicm4"): CIMC_RELEASE_NOTE_SUPPORT_615_M6,
}
# APIC models exempt from the three-node cluster size limit. Keep this
# embedded so the standalone validation script has no external dependency.
APIC_EXCLUDED_MODELS = (
'APIC-SERVER-L1',
'APIC-SERVER-L2',
'APIC-SERVER-L3',
'APIC-SERVER-L4',
'APIC-SERVER-L3T',
'APIC-SERVER-L4T',
'APIC-SERVER-M1',
'APIC-SERVER-M2',
'APIC-SERVER-M3',
'APIC-SERVER-M4',
'APIC-SERVER-M3T',
'APIC-SERVER-M4T',
)
# regex constants
node_regex = r'topology/pod-(?P<pod>\d+)/node-(?P<node>\d+)'
port_regex = node_regex + r'/sys/phys-\[(?P<port>.+)\]'
Expand Down Expand Up @@ -1771,6 +1787,11 @@ def get_fabric_nodes():
return fabricNodes


def get_apic_excluded_models():
"""Return APIC models exempt from the three-node cluster size limit."""
return set(APIC_EXCLUDED_MODELS)


def get_current_versions(fabric_nodes, arg_cversion):
""" Returns: AciVersion instances of APIC and lowest switch """
if arg_cversion:
Expand Down Expand Up @@ -6397,6 +6418,43 @@ def apic_downgrade_compat_warning_check(cversion, tversion, **kwargs):
return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url)


@check_wrapper(check_title='APIC Cluster Size')
def apic_cluster_size_check(tversion, **kwargs):
"""Warn when an APIC cluster has more than three non-excluded models."""
apic_api = ('fabricNode.json?query-target-filter='
'and(eq(fabricNode.role,"controller"),'
'eq(fabricNode.apicType,"apic"),'
'eq(fabricNode.fabricSt,"commissioned"))')
headers = ["Node ID", "Node Name", "Model", "Excluded from 3-Node Limit"]
recommended_action = ('Reduce the APIC cluster to three nodes before continuing '
'the upgrade. Clusters larger than three nodes are not '
'supported for this upgrade unless they include an excluded '
'APIC model.')
doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#apic-cluster-size'

if not tversion:
return Result(result=MANUAL, msg=TVER_MISSING)
if tversion.older_than("6.3(1a)"):
return Result(result=NA, msg=VER_NOT_AFFECTED, doc_url=doc_url)

apics = icurl('class', apic_api)
excluded_models = get_apic_excluded_models()
models = [node.get('fabricNode', {}).get('attributes', {}).get('model', '')
for node in apics]

if len(apics) <= 3 or any(model in excluded_models for model in models):
return Result(result=PASS, msg="APIC cluster size and models are supported.")

data = []
for node in apics:
attributes = node.get('fabricNode', {}).get('attributes', {})
model = attributes.get('model', '')
data.append([attributes.get('id', ''), attributes.get('name', ''), model,
'yes' if model in excluded_models else 'no'])
return Result(result=MANUAL, msg="APIC clusters require no more than 3 nodes unless an excluded model is present.",
headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url)


@check_wrapper(check_title='Auto Firmware Update on Switch Discovery')
def auto_firmware_update_on_switch_check(cversion, tversion, **kwargs):
result = PASS
Expand Down Expand Up @@ -7081,6 +7139,7 @@ class CheckManager:
validate_32_64_bit_image_check,
fabric_link_redundancy_check,
apic_downgrade_compat_warning_check,
apic_cluster_size_check,
svccore_excessive_data_check,

# Faults
Expand Down
9 changes: 9 additions & 0 deletions docs/docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Items | This Script
[Fabric Link Redundancy][g17] | :white_check_mark: | :no_entry_sign:
[APIC Database Size][g18] | :white_check_mark: | :no_entry_sign:
[APIC downgrade compatibility when crossing 6.2 release][g19]| :white_check_mark: | :no_entry_sign:
[APIC Cluster Size][g22] | :white_check_mark: | :no_entry_sign:
[Supported Hardware Compatibility][g20] | :white_check_mark: | :no_entry_sign:
[Svccore Excessive Data Check][g21] | :white_check_mark: | :no_entry_sign:

Expand All @@ -61,6 +62,7 @@ Items | This Script
[g19]: #apic-downgrade-compatibility-when-crossing-62-release
[g20]: #supported-hardware-compatibility
[g21]: #svccore-excessive-data-check
[g22]: #apic-cluster-size

### Fault Checks
Items | Faults | This Script | APIC built-in
Expand Down Expand Up @@ -569,6 +571,13 @@ This check alerts you if you are crossing the 6.2 boundary, beyond which downgra
If it's for a lab environment, you can initialize the fabric and perform a fresh ISO installation of pre-6.2(1) on APICs.


### APIC Cluster Size

For target versions 6.3(1a) and later, APIC clusters must contain no more than three commissioned APIC controllers to continue an upgrade unless they include an APIC-SERVER-L1 through L4 or APIC-SERVER-M1 through M4 model. This validation queries commissioned APIC controllers and compares their models with the excluded models embedded in the validation script.

If more than three controllers are found and none is one of the excluded models, reduce the cluster to three nodes before continuing the upgrade.


## Fault Check Details

### APIC Disk Space Usage
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import importlib

import pytest

script = importlib.import_module("aci-preupgrade-validation-script")

test_function = "apic_cluster_size_check"
apic_api = ('fabricNode.json?query-target-filter='
'and(eq(fabricNode.role,"controller"),'
'eq(fabricNode.apicType,"apic"),'
'eq(fabricNode.fabricSt,"commissioned"))')


def fabric_nodes(models):
return [{"fabricNode": {"attributes": {
"id": str(index + 1), "name": "apic{}".format(index + 1), "model": model,
}}} for index, model in enumerate(models)]


@pytest.mark.parametrize(
"models, expected_result",
[
(["APIC-SERVER-G5"] * 3, script.PASS),
(["APIC-SERVER-G5"] * 4, script.MANUAL),
(["APIC-SERVER-G5"] * 4 + ["APIC-SERVER-L2"], script.PASS),
(["APIC-SERVER-L1"] * 4, script.PASS),
(["APIC-SERVER-M2"] * 4, script.PASS),
(["APIC-SERVER-M1"] * 4, script.PASS),
(["APIC-SERVER-L4T"] * 4, script.PASS),
(["APIC-SERVER-M4T"] * 4, script.PASS),
(["APIC-SERVER-G5"] * 4, script.MANUAL),
(["APIC-SERVER-G5T"] * 4, script.MANUAL),
],
)
def test_logic(monkeypatch, models, expected_result):
monkeypatch.setattr(script, "icurl", lambda apitype, query: fabric_nodes(models))
result = script.apic_cluster_size_check(
tversion=script.AciVersion("6.3(1a)"),
finalize_check=lambda check_id, result: None,
)
assert result.result == expected_result


def test_reports_models_and_recommended_action(monkeypatch):
monkeypatch.setattr(script, "icurl", lambda apitype, query: fabric_nodes(["APIC-SERVER-G5"] * 4))
result = script.apic_cluster_size_check(
tversion=script.AciVersion("6.3(1a)"),
finalize_check=lambda check_id, result: None,
)
assert result.headers == ["Node ID", "Node Name", "Model", "Excluded from 3-Node Limit"]
assert result.data == [
[str(index), "apic{}".format(index), "APIC-SERVER-G5", "no"]
for index in range(1, 5)
]
assert "Reduce the APIC cluster to three nodes" in result.recommended_action


@pytest.mark.parametrize("target_version", ["6.2(7f)", "6.3(0a)"])
def test_older_targets_are_not_blocked(monkeypatch, target_version):
def unexpected_api_call(*args):
pytest.fail("APIC cluster size should not be checked for this target")

monkeypatch.setattr(script, "icurl", unexpected_api_call)
result = script.apic_cluster_size_check(
tversion=script.AciVersion(target_version),
finalize_check=lambda check_id, result: None,
)
assert result.result == script.NA


def test_missing_target_version_is_manual():
result = script.apic_cluster_size_check(
tversion=None,
finalize_check=lambda check_id, result: None,
)
assert result.result == script.MANUAL
assert result.msg == script.TVER_MISSING