From d96fdea8e79498b0ab1ea923ed69d94055d0d7e5 Mon Sep 17 00:00:00 2001 From: tkishida Date: Mon, 3 Nov 2025 22:30:28 -0800 Subject: [PATCH 01/16] Support multithread execution --- aci-preupgrade-validation-script.py | 844 +++++++++++++----- pytest.ini | 2 +- .../access_untagged_check/faultInst_NEG.json | 0 .../access_untagged_check/faultInst_POS.json | 0 .../test_access_untagged_check.py | 7 +- .../aes_encryption_check/exportcryptkey.json | 0 .../exportcryptkey_disabled.json | 0 .../test_aes_encryption_check.py | 8 +- .../apic_ca_cert_validation/NEG_certreq.txt | 0 .../apic_ca_cert_validation/POS_certreq.txt | 0 .../test_apic_ca_cert_validation.py | 10 +- .../infraWiNode_3.json | 0 .../infraWiNode_4.json | 0 .../test_apic_database_size_check.py | 22 +- .../firmwareFirmware_6.0.5h.json | 0 ...rmwareFirmware_6.0.5h_image_sign_fail.json | 0 .../test_apic_version_md5_check.py | 13 +- .../apic_version_md5_check/topSystem.json | 0 .../ave_eol_check/test_ave_eol_check.py | 11 +- .../ave_eol_check/vmmDomP_POS.json | 0 .../fvCtx_pos.json | 0 .../test_bgp_golf_route_target_type_check.py | 13 +- .../l3extRsNodeL3OutAtt_neg.json | 0 .../l3extRsNodeL3OutAtt_pos.json | 0 .../l3extRsNodeL3OutAtt_pos1.json | 0 .../l3extRsNodeL3OutAtt_pos2.json | 0 .../test_bgp_peer_loopback_check.py | 7 +- .../compatRsSuppHw_605_L2.json | 0 .../compatRsSuppHw_605_M1.json | 0 .../compatRsSuppHw_empty.json | 0 .../eqptCh_newver.json | 0 .../eqptCh_oldver.json | 0 .../eqptCh_reallyoldver.json | 0 .../test_cimc_compatibilty_check.py | 24 +- .../eqptFC_NEG.json | 0 .../eqptFC_POS.json | 0 .../eqptLC_NEG.json | 0 .../eqptLC_POS.json | 0 ...st_clock_signal_component_failure_check.py | 7 +- .../cloudsecPreSharedKey_err.json | 0 .../cloudsecPreSharedKey_neg.json | 0 .../cloudsecPreSharedKey_pos.json | 0 .../cloudsecPreSharedKey_pos2.json | 0 .../test_cloudsec_encryption_depr_check.py | 7 +- .../configpushShardCont_pos.json | 0 .../test_configpush_shard_check.py | 12 +- tests/checks/conftest.py | 137 +++ .../epg_epg2_unmatched.json | 0 .../esg_esg2.json | 0 .../fvCtx_consumer_same_vrf.json | 0 .../fvCtx_consumer_shared.json | 0 .../fvCtx_no_consumers.json | 0 .../global_contracts_epg_only.json | 0 .../global_contracts_esg_only.json | 0 .../global_contracts_same_vrf.json | 0 .../global_contracts_shared.json | 0 .../instp_l3instp2.json | 0 ...st_consumer_vzany_shared_services_check.py | 13 +- .../vnsGraphInst_redirect.json | 0 .../test_contract_22_defect_check.py | 14 +- .../apContainerPol_10_0_0_1__16.json | 0 .../apContainerPol_172_16_0_1__15.json | 0 .../apContainerPol_172_17_0_10__16.json | 0 .../apContainerPol_172_17_0_1__16.json | 0 .../apContainerPol_172_17_0_1__17.json | 0 .../apContainerPol_172_18_0_1__16.json | 0 .../infraWiNode_10_0_0_0__16.json | 0 .../infraWiNode_10_0_x_0__24_remote_apic.json | 0 .../infraWiNode_172_17_0_0__16.json | 0 ...nfraWiNode_172_17_x_0__24_remote_apic.json | 0 .../test_docker0_subent_overlap_check.py | 8 +- .../eecdh_cipher_check/commCipher_neg.json | 0 .../eecdh_cipher_check/commCipher_neg2.json | 0 .../eecdh_cipher_check/commCipher_pos.json | 0 .../test_eecdh_cipher_check.py | 11 +- .../faultInst-encap-pos.json | 0 .../faultInst-new-version.json | 0 .../encap_already_in_use_check/fvIfConn.json | 0 .../test_encap_already_in_use_check.py | 7 +- .../faultInst_neg.json | 0 .../faultInst_pos.json | 0 .../test_equipment_disk_limits_exceeded.py | 9 +- .../test_eventmgr_db_defect_check.py | 10 +- .../fabricRsOosPath_neg.json | 0 .../fabricRsOosPath_pos1.json | 0 .../fabricRsOosPath_pos2.json | 0 .../fabricRsOosPath_pos3.json | 0 .../fabricRsOosPath_pos4.json | 0 .../fabricRsOosPath_pos5.json | 0 .../fabricRsOosPath_pos6.json | 0 .../infraRsHPathAtt_neg.json | 0 .../infraRsHPathAtt_pos1.json | 0 .../infraRsHPathAtt_pos2.json | 0 .../infraRsHPathAtt_pos3.json | 0 .../infraRsHPathAtt_pos4.json | 0 .../infraRsHPathAtt_pos5.json | 0 .../infraRsHPathAtt_pos6.json | 0 .../test_fabricPathEP_target_check.py | 12 +- .../fabric_dpp_check/lbpPol_NEG.json | 0 .../fabric_dpp_check/lbpPol_POS.json | 0 .../fabric_dpp_check/test_fabric_dpp_check.py | 12 +- .../fabricNode.json | 0 .../lldpAdjEp_neg.json | 0 .../lldpAdjEp_pos_spine_only.json | 0 .../lldpAdjEp_pos_spine_t1.json | 0 .../lldpAdjEp_pos_t1_only.json | 0 .../test_fabric_link_redundancy_check.py | 7 +- .../fabric_port_down_check/faultInst_pos.json | 0 .../test_fabric_port_down_check.py | 9 +- .../test_fabricdomain_name_check.py | 10 +- .../topSystem_1POS.json | 0 .../topSystem_2POS.json | 0 .../topSystem_NEG.json | 0 .../fc_ex_model_check/fabricNode_NEG.json | 0 .../fc_ex_model_check/fabricNode_POS.json | 0 .../fc_ex_model_check/fcEntity_NEG.json | 0 .../fc_ex_model_check/fcEntity_POS.json | 0 .../test_fc_ex_model_check.py | 8 +- tests/{ => checks}/helpers/__init__.py | 0 tests/{ => checks}/helpers/utils.py | 2 +- .../commHttps_neg1.json | 0 .../commHttps_neg2.json | 0 .../commHttps_pos.json | 0 .../test_https_throttle_rate_check.py | 12 +- .../fvnsVlanInstP_neg.json | 0 .../fvnsVlanInstP_pos.json | 0 .../test_internal_vlanpool_check.py | 7 +- .../internal_vlanpool_check/vmmDomP_neg.json | 0 .../internal_vlanpool_check/vmmDomP_pos.json | 0 .../isisDTEp_NEG.json | 0 .../isisDTEp_POS.json | 0 .../test_isis_database_byte_check.py | 12 +- .../fvFabricExtConnP_pos1.json | 0 .../fvFabricExtConnP_pos2.json | 0 .../fvFabricExtConnP_pos3.json | 0 .../isisDomP-default_missing.json | 0 .../isisDomP-default_neg.json | 0 .../isisDomP-default_pos.json | 0 ...test_isis_redis_metric_mpod_msite_check.py | 7 +- .../l3out_mtu_check/l2pol-default.json | 0 .../l3out_mtu_check/l3extRsPathL3OutAtt.json | 0 .../l3out_mtu_check/l3extVirtualLIfP.json | 0 .../l3extVirtualLIfP_unresolved.json | 0 .../l3out_mtu_check/test_l3out_mtu_check.py | 7 +- .../diff_l3out_loopback.json | 0 .../diff_l3out_loopback_and_rtrId.json | 0 .../diff_l3out_rtrId.json | 0 .../no_overlap.json | 0 .../overlap_on_diff_nodes.json | 0 .../same_l3out_loopback.json | 0 .../same_l3out_loopback_and_rtrId.json | 0 .../same_l3out_loopback_with_subnet_mask.json | 0 .../same_l3out_rtrId.json | 0 .../same_l3out_rtrId_non_vpc.json | 0 .../same_l3out_two_loopbacks.json | 0 .../test_l3out_overlapping_loopback_check.py | 7 +- .../rtctrlProfile_missing_target.json | 0 ...ultiple_l3out_multiple_missing_target.json | 0 ...rtctrlProfile_multiple_missing_target.json | 0 .../rtctrlProfile_no_missing_target.json | 0 ...st_l3out_route_map_missing_target_check.py | 10 +- .../fvRsDomAtt_neg.json | 0 .../fvRsDomAtt_pos.json | 0 .../infraPortBlk_neg.json | 0 .../infraPortBlk_pos.json | 0 ...ldp_custom_int_description_defect_check.py | 7 +- .../llfc_susceptibility_check/ethpmFcot.json | 0 .../test_llfc_susceptibility_check.py | 13 +- .../test_mini_aci_6_0_2_check.py | 13 +- .../topSystem_controller_neg.json | 0 .../topSystem_controller_pos.json | 0 .../n9408_model_check/eqptCh_NEG.json | 0 .../n9408_model_check/eqptCh_POS.json | 0 .../test_n9408_model_check.py | 8 +- .../fabricNode_FX3H.json | 0 .../fabricNode_FX3P.json | 0 .../fabricNode_FX3P3H.json | 0 ..._n9k_c93108tc_fx3p_interface_down_check.py | 11 +- .../test_observer_db_size_check.py | 9 +- .../observer_db_size_check/topSystem.json | 0 .../topSystem_empty.json | 0 .../oob_mgmt_security_check/mgmtInstP.json | 0 .../mgmtInstP_no_contracts.json | 0 .../mgmtInstP_no_subnets.json | 0 .../oob_mgmt_security_check/mgmtOoB.json | 0 .../mgmtOoB_no_contracts.json | 0 .../test_oob_mgmt_security_check.py | 12 +- .../ethpmPhysIf-neg.json | 0 .../ethpmPhysIf-pos.json | 0 .../test_out_of_service_ports_check.py | 12 +- .../access_policy.json | 0 .../overlapping_vlan_pools_check/fvAEPg.json | 0 .../fvIfConn.json | 0 .../infraSetPol_no.json | 0 .../infraSetPol_yes.json | 0 .../templates/access_policy.j2 | 0 .../templates/fvAEPg.j2 | 0 .../templates/fvIfConn.j2 | 0 .../templates/macros.j2 | 0 .../test_overlapping_vlan_pools_check.py | 18 +- .../test_pbr_high_scale_check.py | 12 +- .../vnsAdjacencyDefCont_HIGH.json | 0 .../vnsAdjacencyDefCont_LOW.json | 0 .../vnsSvcRedirEcmpBucketCons_HIGH.json | 0 .../vnsSvcRedirEcmpBucketCons_LOW.json | 0 .../post_upgrade_cb_check/moCount_0.json | 0 .../post_upgrade_cb_check/moCount_10.json | 0 .../post_upgrade_cb_check/moCount_8.json | 0 .../test_post_upgrade_cb_check.py | 15 +- ...F0467_prefix-entry-already-in-use_new.json | 0 ...F0467_prefix-entry-already-in-use_old.json | 0 .../prefix_already_in_use_check/fvCtx.json | 0 .../l3extRsEctx.json | 0 .../l3extSubnet_no_overlap.json | 0 .../l3extSubnet_overlap.json | 0 .../test_prefix_already_in_use_check.py | 7 +- .../rtctrlCtxP_NEG.json | 0 .../rtctrlCtxP_POS.json | 0 .../rtctrlSubjP_NEG.json | 0 .../rtctrlSubjP_POS.json | 0 .../test_rtmap_comm_match_defect_check.py | 12 +- .../fvRtEPpInfoToBD.json | 0 .../test_service_bd_forceful_routing_check.py | 12 +- .../fabricRsDecommissionNode_NEG.json | 0 .../fabricRsDecommissionNode_POS.json | 0 .../test_stale_decomissioned_spine_check.py | 16 +- .../topSystem.json | 0 .../standby_sup_sync_check/eqptSupC_NEG.json | 0 .../standby_sup_sync_check/eqptSupC_POS.json | 0 .../test_standby_sup_sync_check.py | 153 ++++ .../static_route_overlap_check/fvRsCtx.json | 0 .../static_route_overlap_check/fvSubnet.json | 0 .../ipRouteP_empty.json | 0 .../ipRouteP_neg.json | 0 .../ipRouteP_pos.json | 0 .../l3extRsEctx.json | 0 .../test_static_route_overlap_check.py | 91 ++ .../subnet_scope_check/fvAEPg_empty.json | 0 .../subnet_scope_check/fvAEPg_neg.json | 0 .../subnet_scope_check/fvAEPg_pos.json | 0 .../{ => checks}/subnet_scope_check/fvBD.json | 0 .../subnet_scope_check/fvRsBd.json | 0 .../test_subnet_scope_check.py | 74 ++ .../eqptSupC_SUP_A.json | 0 .../eqptSupC_SUP_A_Aplus.json | 0 .../eqptSupC_SUP_Aplus.json | 0 .../eqptSupC_no_SUP_A_Aplus.json | 0 .../test_sup_a_high_memory_check.py | 7 +- .../sup_hwrev_check/eqptSpCmnBlk_NEG.json | 0 .../sup_hwrev_check/eqptSpCmnBlk_POS.json | 0 .../sup_hwrev_check/test_sup_hwrev_check.py | 10 +- .../eqptcapacityFSPartition.json | 0 .../maintUpgJob_not_downloaded.json | 0 .../maintUpgJob_old_ver_no_prop.json | 0 .../maintUpgJob_pre_downloaded.json | 0 .../test_switch_bootflash_usage_check.py | 54 ++ .../telemetryStatsServerP_neg.json | 0 .../telemetryStatsServerP_pos.json | 0 ...test_telemetryStatsServerP_object_check.py | 13 +- .../dbgAcPath_max.json | 0 .../dbgAcPath_na.json | 0 .../dbgAcPath_pass.json | 0 .../test_tep_to_tep_ac_count_check.py | 23 +- ..._unsupported_fec_configuration_ex_check.py | 14 +- .../topSystem_neg.json | 0 .../topSystem_pos.json | 0 .../uplink_limit_check/eqptPortP_NEG.json | 0 .../uplink_limit_check/eqptPortP_POS.json | 0 .../uplink_limit_check/eqptPortP_empty.json | 0 .../test_uplink_limit_check.py | 10 +- .../firmwareFirmware_empty.json | 0 .../firmwareFirmware_neg.json | 0 .../firmwareFirmware_pos.json | 0 .../firmwareFirmware_pos2.json | 0 .../firmwareFirmware_pos3.json | 0 .../firmwareFirmware_pos4.json | 0 .../test_validate_32_64_bit_image_check.py | 69 +- .../fvUplinkOrderCont_neg.json | 0 .../fvUplinkOrderCont_not_exist.json | 0 .../fvUplinkOrderCont_pos.json | 0 .../test_vmm_active_uplinks_check.py | 9 +- .../test_vpc_paired_switches_check.py | 7 +- .../vpc_paired_switches_check/topSystem.json | 0 .../test_vzany_vzany_service_epg_check.py | 13 +- .../vzRsSubjGraphAtt.json | 0 .../vzRtAny_vzAny_prov_cons_diff_VRFs.json | 0 .../vzRtAny_vzAny_prov_only.json | 0 .../vzRtAny_vzAny_vzAny.json | 0 .../vzRtAny_vzAny_vzAny_2_VRFs.json | 0 tests/conftest.py | 318 ++++--- .../test_standby_sup_sync_check.py | 174 ---- .../test_static_route_overlap_check.py | 67 -- .../test_subnet_scope_check.py | 63 -- .../test_switch_bootflash_usage_check.py | 46 - tests/test_AciResult.py | 214 ++--- tests/test_CheckManager.py | 353 ++++++++ tests/test_ResultManager.py | 123 +++ tests/test_ThreadManager.py | 39 + tests/test_common_data.py | 250 ++++++ tests/test_main.py | 148 ++- tests/test_prepare.py | 327 ------- tests/test_run_checks.py | 188 ---- 302 files changed, 2728 insertions(+), 1624 deletions(-) rename tests/{ => checks}/access_untagged_check/faultInst_NEG.json (100%) rename tests/{ => checks}/access_untagged_check/faultInst_POS.json (100%) rename tests/{ => checks}/access_untagged_check/test_access_untagged_check.py (80%) rename tests/{ => checks}/aes_encryption_check/exportcryptkey.json (100%) rename tests/{ => checks}/aes_encryption_check/exportcryptkey_disabled.json (100%) rename tests/{ => checks}/aes_encryption_check/test_aes_encryption_check.py (87%) rename tests/{ => checks}/apic_ca_cert_validation/NEG_certreq.txt (100%) rename tests/{ => checks}/apic_ca_cert_validation/POS_certreq.txt (100%) rename tests/{ => checks}/apic_ca_cert_validation/test_apic_ca_cert_validation.py (69%) rename tests/{ => checks}/apic_database_size_check/infraWiNode_3.json (100%) rename tests/{ => checks}/apic_database_size_check/infraWiNode_4.json (100%) rename tests/{ => checks}/apic_database_size_check/test_apic_database_size_check.py (96%) rename tests/{ => checks}/apic_version_md5_check/firmwareFirmware_6.0.5h.json (100%) rename tests/{ => checks}/apic_version_md5_check/firmwareFirmware_6.0.5h_image_sign_fail.json (100%) rename tests/{ => checks}/apic_version_md5_check/test_apic_version_md5_check.py (96%) rename tests/{ => checks}/apic_version_md5_check/topSystem.json (100%) rename tests/{ => checks}/ave_eol_check/test_ave_eol_check.py (81%) rename tests/{ => checks}/ave_eol_check/vmmDomP_POS.json (100%) rename tests/{ => checks}/bgp_golf_route_target_type_check/fvCtx_pos.json (100%) rename tests/{ => checks}/bgp_golf_route_target_type_check/test_bgp_golf_route_target_type_check.py (83%) rename tests/{ => checks}/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_neg.json (100%) rename tests/{ => checks}/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos.json (100%) rename tests/{ => checks}/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos1.json (100%) rename tests/{ => checks}/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos2.json (100%) rename tests/{ => checks}/bgp_peer_loopback_check/test_bgp_peer_loopback_check.py (87%) rename tests/{ => checks}/cimc_compatibilty_check/compatRsSuppHw_605_L2.json (100%) rename tests/{ => checks}/cimc_compatibilty_check/compatRsSuppHw_605_M1.json (100%) rename tests/{ => checks}/cimc_compatibilty_check/compatRsSuppHw_empty.json (100%) rename tests/{ => checks}/cimc_compatibilty_check/eqptCh_newver.json (100%) rename tests/{ => checks}/cimc_compatibilty_check/eqptCh_oldver.json (100%) rename tests/{ => checks}/cimc_compatibilty_check/eqptCh_reallyoldver.json (100%) rename tests/{ => checks}/cimc_compatibilty_check/test_cimc_compatibilty_check.py (60%) rename tests/{ => checks}/clock_signal_component_failure_check/eqptFC_NEG.json (100%) rename tests/{ => checks}/clock_signal_component_failure_check/eqptFC_POS.json (100%) rename tests/{ => checks}/clock_signal_component_failure_check/eqptLC_NEG.json (100%) rename tests/{ => checks}/clock_signal_component_failure_check/eqptLC_POS.json (100%) rename tests/{ => checks}/clock_signal_component_failure_check/test_clock_signal_component_failure_check.py (88%) rename tests/{ => checks}/cloudsec_encryption_depr_check/cloudsecPreSharedKey_err.json (100%) rename tests/{ => checks}/cloudsec_encryption_depr_check/cloudsecPreSharedKey_neg.json (100%) rename tests/{ => checks}/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos.json (100%) rename tests/{ => checks}/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos2.json (100%) rename tests/{ => checks}/cloudsec_encryption_depr_check/test_cloudsec_encryption_depr_check.py (89%) rename tests/{ => checks}/configpush_shard_check/configpushShardCont_pos.json (100%) rename tests/{ => checks}/configpush_shard_check/test_configpush_shard_check.py (86%) create mode 100644 tests/checks/conftest.py rename tests/{ => checks}/consumer_vzany_shared_services_check/epg_epg2_unmatched.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/esg_esg2.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/fvCtx_consumer_same_vrf.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/fvCtx_consumer_shared.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/fvCtx_no_consumers.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/global_contracts_epg_only.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/global_contracts_esg_only.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/global_contracts_same_vrf.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/global_contracts_shared.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/instp_l3instp2.json (100%) rename tests/{ => checks}/consumer_vzany_shared_services_check/test_consumer_vzany_shared_services_check.py (97%) rename tests/{ => checks}/consumer_vzany_shared_services_check/vnsGraphInst_redirect.json (100%) rename tests/{ => checks}/contract_22_defect_check/test_contract_22_defect_check.py (65%) rename tests/{ => checks}/docker0_subent_overlap_check/apContainerPol_10_0_0_1__16.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/apContainerPol_172_16_0_1__15.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/apContainerPol_172_17_0_10__16.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/apContainerPol_172_17_0_1__16.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/apContainerPol_172_17_0_1__17.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/apContainerPol_172_18_0_1__16.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/infraWiNode_10_0_0_0__16.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/infraWiNode_10_0_x_0__24_remote_apic.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/infraWiNode_172_17_0_0__16.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/infraWiNode_172_17_x_0__24_remote_apic.json (100%) rename tests/{ => checks}/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py (96%) rename tests/{ => checks}/eecdh_cipher_check/commCipher_neg.json (100%) rename tests/{ => checks}/eecdh_cipher_check/commCipher_neg2.json (100%) rename tests/{ => checks}/eecdh_cipher_check/commCipher_pos.json (100%) rename tests/{ => checks}/eecdh_cipher_check/test_eecdh_cipher_check.py (80%) rename tests/{ => checks}/encap_already_in_use_check/faultInst-encap-pos.json (100%) rename tests/{ => checks}/encap_already_in_use_check/faultInst-new-version.json (100%) rename tests/{ => checks}/encap_already_in_use_check/fvIfConn.json (100%) rename tests/{ => checks}/encap_already_in_use_check/test_encap_already_in_use_check.py (86%) rename tests/{ => checks}/equipment_disk_limits_exceeded/faultInst_neg.json (100%) rename tests/{ => checks}/equipment_disk_limits_exceeded/faultInst_pos.json (100%) rename tests/{ => checks}/equipment_disk_limits_exceeded/test_equipment_disk_limits_exceeded.py (79%) rename tests/{ => checks}/eventmgr_db_defect_check/test_eventmgr_db_defect_check.py (77%) rename tests/{ => checks}/fabricPathEP_target_check/fabricRsOosPath_neg.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/fabricRsOosPath_pos1.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/fabricRsOosPath_pos2.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/fabricRsOosPath_pos3.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/fabricRsOosPath_pos4.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/fabricRsOosPath_pos5.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/fabricRsOosPath_pos6.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/infraRsHPathAtt_neg.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/infraRsHPathAtt_pos1.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/infraRsHPathAtt_pos2.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/infraRsHPathAtt_pos3.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/infraRsHPathAtt_pos4.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/infraRsHPathAtt_pos5.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/infraRsHPathAtt_pos6.json (100%) rename tests/{ => checks}/fabricPathEP_target_check/test_fabricPathEP_target_check.py (89%) rename tests/{ => checks}/fabric_dpp_check/lbpPol_NEG.json (100%) rename tests/{ => checks}/fabric_dpp_check/lbpPol_POS.json (100%) rename tests/{ => checks}/fabric_dpp_check/test_fabric_dpp_check.py (85%) rename tests/{ => checks}/fabric_link_redundancy_check/fabricNode.json (100%) rename tests/{ => checks}/fabric_link_redundancy_check/lldpAdjEp_neg.json (100%) rename tests/{ => checks}/fabric_link_redundancy_check/lldpAdjEp_pos_spine_only.json (100%) rename tests/{ => checks}/fabric_link_redundancy_check/lldpAdjEp_pos_spine_t1.json (100%) rename tests/{ => checks}/fabric_link_redundancy_check/lldpAdjEp_pos_t1_only.json (100%) rename tests/{ => checks}/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py (91%) rename tests/{ => checks}/fabric_port_down_check/faultInst_pos.json (100%) rename tests/{ => checks}/fabric_port_down_check/test_fabric_port_down_check.py (78%) rename tests/{ => checks}/fabricdomain_name_check/test_fabricdomain_name_check.py (87%) rename tests/{ => checks}/fabricdomain_name_check/topSystem_1POS.json (100%) rename tests/{ => checks}/fabricdomain_name_check/topSystem_2POS.json (100%) rename tests/{ => checks}/fabricdomain_name_check/topSystem_NEG.json (100%) rename tests/{ => checks}/fc_ex_model_check/fabricNode_NEG.json (100%) rename tests/{ => checks}/fc_ex_model_check/fabricNode_POS.json (100%) rename tests/{ => checks}/fc_ex_model_check/fcEntity_NEG.json (100%) rename tests/{ => checks}/fc_ex_model_check/fcEntity_POS.json (100%) rename tests/{ => checks}/fc_ex_model_check/test_fc_ex_model_check.py (89%) rename tests/{ => checks}/helpers/__init__.py (100%) rename tests/{ => checks}/helpers/utils.py (68%) rename tests/{ => checks}/https_throttle_rate_check/commHttps_neg1.json (100%) rename tests/{ => checks}/https_throttle_rate_check/commHttps_neg2.json (100%) rename tests/{ => checks}/https_throttle_rate_check/commHttps_pos.json (100%) rename tests/{ => checks}/https_throttle_rate_check/test_https_throttle_rate_check.py (88%) rename tests/{ => checks}/internal_vlanpool_check/fvnsVlanInstP_neg.json (100%) rename tests/{ => checks}/internal_vlanpool_check/fvnsVlanInstP_pos.json (100%) rename tests/{ => checks}/internal_vlanpool_check/test_internal_vlanpool_check.py (93%) rename tests/{ => checks}/internal_vlanpool_check/vmmDomP_neg.json (100%) rename tests/{ => checks}/internal_vlanpool_check/vmmDomP_pos.json (100%) rename tests/{ => checks}/isis_database_byte_check/isisDTEp_NEG.json (100%) rename tests/{ => checks}/isis_database_byte_check/isisDTEp_POS.json (100%) rename tests/{ => checks}/isis_database_byte_check/test_isis_database_byte_check.py (88%) rename tests/{ => checks}/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos1.json (100%) rename tests/{ => checks}/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos2.json (100%) rename tests/{ => checks}/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos3.json (100%) rename tests/{ => checks}/isis_redis_metric_mpod_msite_check/isisDomP-default_missing.json (100%) rename tests/{ => checks}/isis_redis_metric_mpod_msite_check/isisDomP-default_neg.json (100%) rename tests/{ => checks}/isis_redis_metric_mpod_msite_check/isisDomP-default_pos.json (100%) rename tests/{ => checks}/isis_redis_metric_mpod_msite_check/test_isis_redis_metric_mpod_msite_check.py (90%) rename tests/{ => checks}/l3out_mtu_check/l2pol-default.json (100%) rename tests/{ => checks}/l3out_mtu_check/l3extRsPathL3OutAtt.json (100%) rename tests/{ => checks}/l3out_mtu_check/l3extVirtualLIfP.json (100%) rename tests/{ => checks}/l3out_mtu_check/l3extVirtualLIfP_unresolved.json (100%) rename tests/{ => checks}/l3out_mtu_check/test_l3out_mtu_check.py (92%) rename tests/{ => checks}/l3out_overlapping_loopback_check/diff_l3out_loopback.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/diff_l3out_loopback_and_rtrId.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/diff_l3out_rtrId.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/no_overlap.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/overlap_on_diff_nodes.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/same_l3out_loopback.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/same_l3out_loopback_and_rtrId.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/same_l3out_loopback_with_subnet_mask.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/same_l3out_rtrId.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/same_l3out_rtrId_non_vpc.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/same_l3out_two_loopbacks.json (100%) rename tests/{ => checks}/l3out_overlapping_loopback_check/test_l3out_overlapping_loopback_check.py (92%) rename tests/{ => checks}/l3out_route_map_missing_target_check/rtctrlProfile_missing_target.json (100%) rename tests/{ => checks}/l3out_route_map_missing_target_check/rtctrlProfile_multiple_l3out_multiple_missing_target.json (100%) rename tests/{ => checks}/l3out_route_map_missing_target_check/rtctrlProfile_multiple_missing_target.json (100%) rename tests/{ => checks}/l3out_route_map_missing_target_check/rtctrlProfile_no_missing_target.json (100%) rename tests/{ => checks}/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py (86%) rename tests/{ => checks}/lldp_custom_int_description_defect_check/fvRsDomAtt_neg.json (100%) rename tests/{ => checks}/lldp_custom_int_description_defect_check/fvRsDomAtt_pos.json (100%) rename tests/{ => checks}/lldp_custom_int_description_defect_check/infraPortBlk_neg.json (100%) rename tests/{ => checks}/lldp_custom_int_description_defect_check/infraPortBlk_pos.json (100%) rename tests/{ => checks}/lldp_custom_int_description_defect_check/test_lldp_custom_int_description_defect_check.py (91%) rename tests/{ => checks}/llfc_susceptibility_check/ethpmFcot.json (100%) rename tests/{ => checks}/llfc_susceptibility_check/test_llfc_susceptibility_check.py (86%) rename tests/{mini_aci_6_0_2 => checks/mini_aci_6_0_2_check}/test_mini_aci_6_0_2_check.py (84%) rename tests/{mini_aci_6_0_2 => checks/mini_aci_6_0_2_check}/topSystem_controller_neg.json (100%) rename tests/{mini_aci_6_0_2 => checks/mini_aci_6_0_2_check}/topSystem_controller_pos.json (100%) rename tests/{ => checks}/n9408_model_check/eqptCh_NEG.json (100%) rename tests/{ => checks}/n9408_model_check/eqptCh_POS.json (100%) rename tests/{ => checks}/n9408_model_check/test_n9408_model_check.py (82%) rename tests/{ => checks}/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json (100%) rename tests/{ => checks}/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json (100%) rename tests/{ => checks}/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json (100%) rename tests/{ => checks}/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py (87%) rename tests/{ => checks}/observer_db_size_check/test_observer_db_size_check.py (93%) rename tests/{ => checks}/observer_db_size_check/topSystem.json (100%) rename tests/{ => checks}/observer_db_size_check/topSystem_empty.json (100%) rename tests/{ => checks}/oob_mgmt_security_check/mgmtInstP.json (100%) rename tests/{ => checks}/oob_mgmt_security_check/mgmtInstP_no_contracts.json (100%) rename tests/{ => checks}/oob_mgmt_security_check/mgmtInstP_no_subnets.json (100%) rename tests/{ => checks}/oob_mgmt_security_check/mgmtOoB.json (100%) rename tests/{ => checks}/oob_mgmt_security_check/mgmtOoB_no_contracts.json (100%) rename tests/{ => checks}/oob_mgmt_security_check/test_oob_mgmt_security_check.py (92%) rename tests/{ => checks}/out_of_service_ports_check/ethpmPhysIf-neg.json (100%) rename tests/{ => checks}/out_of_service_ports_check/ethpmPhysIf-pos.json (100%) rename tests/{ => checks}/out_of_service_ports_check/test_out_of_service_ports_check.py (75%) rename tests/{ => checks}/overlapping_vlan_pools_check/access_policy.json (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/fvAEPg.json (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/fvIfConn.json (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/infraSetPol_no.json (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/infraSetPol_yes.json (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/templates/access_policy.j2 (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/templates/fvAEPg.j2 (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/templates/fvIfConn.j2 (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/templates/macros.j2 (100%) rename tests/{ => checks}/overlapping_vlan_pools_check/test_overlapping_vlan_pools_check.py (99%) rename tests/{ => checks}/pbr_high_scale_check/test_pbr_high_scale_check.py (90%) rename tests/{ => checks}/pbr_high_scale_check/vnsAdjacencyDefCont_HIGH.json (100%) rename tests/{ => checks}/pbr_high_scale_check/vnsAdjacencyDefCont_LOW.json (100%) rename tests/{ => checks}/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_HIGH.json (100%) rename tests/{ => checks}/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_LOW.json (100%) rename tests/{ => checks}/post_upgrade_cb_check/moCount_0.json (100%) rename tests/{ => checks}/post_upgrade_cb_check/moCount_10.json (100%) rename tests/{ => checks}/post_upgrade_cb_check/moCount_8.json (100%) rename tests/{ => checks}/post_upgrade_cb_check/test_post_upgrade_cb_check.py (92%) rename tests/{ => checks}/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_new.json (100%) rename tests/{ => checks}/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_old.json (100%) rename tests/{ => checks}/prefix_already_in_use_check/fvCtx.json (100%) rename tests/{ => checks}/prefix_already_in_use_check/l3extRsEctx.json (100%) rename tests/{ => checks}/prefix_already_in_use_check/l3extSubnet_no_overlap.json (100%) rename tests/{ => checks}/prefix_already_in_use_check/l3extSubnet_overlap.json (100%) rename tests/{ => checks}/prefix_already_in_use_check/test_prefix_already_in_use_check.py (91%) rename tests/{ => checks}/rtmap_comm_match_defect_check/rtctrlCtxP_NEG.json (100%) rename tests/{ => checks}/rtmap_comm_match_defect_check/rtctrlCtxP_POS.json (100%) rename tests/{ => checks}/rtmap_comm_match_defect_check/rtctrlSubjP_NEG.json (100%) rename tests/{ => checks}/rtmap_comm_match_defect_check/rtctrlSubjP_POS.json (100%) rename tests/{ => checks}/rtmap_comm_match_defect_check/test_rtmap_comm_match_defect_check.py (88%) rename tests/{ => checks}/service_bd_forceful_routing_check/fvRtEPpInfoToBD.json (100%) rename tests/{ => checks}/service_bd_forceful_routing_check/test_service_bd_forceful_routing_check.py (81%) rename tests/{ => checks}/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json (100%) rename tests/{ => checks}/stale_decomissioned_spine_check/fabricRsDecommissionNode_POS.json (100%) rename tests/{ => checks}/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py (80%) rename tests/{ => checks}/stale_decomissioned_spine_check/topSystem.json (100%) rename tests/{ => checks}/standby_sup_sync_check/eqptSupC_NEG.json (100%) rename tests/{ => checks}/standby_sup_sync_check/eqptSupC_POS.json (100%) create mode 100644 tests/checks/standby_sup_sync_check/test_standby_sup_sync_check.py rename tests/{ => checks}/static_route_overlap_check/fvRsCtx.json (100%) rename tests/{ => checks}/static_route_overlap_check/fvSubnet.json (100%) rename tests/{ => checks}/static_route_overlap_check/ipRouteP_empty.json (100%) rename tests/{ => checks}/static_route_overlap_check/ipRouteP_neg.json (100%) rename tests/{ => checks}/static_route_overlap_check/ipRouteP_pos.json (100%) rename tests/{ => checks}/static_route_overlap_check/l3extRsEctx.json (100%) create mode 100644 tests/checks/static_route_overlap_check/test_static_route_overlap_check.py rename tests/{ => checks}/subnet_scope_check/fvAEPg_empty.json (100%) rename tests/{ => checks}/subnet_scope_check/fvAEPg_neg.json (100%) rename tests/{ => checks}/subnet_scope_check/fvAEPg_pos.json (100%) rename tests/{ => checks}/subnet_scope_check/fvBD.json (100%) rename tests/{ => checks}/subnet_scope_check/fvRsBd.json (100%) create mode 100644 tests/checks/subnet_scope_check/test_subnet_scope_check.py rename tests/{ => checks}/sup_a_high_memory_check/eqptSupC_SUP_A.json (100%) rename tests/{ => checks}/sup_a_high_memory_check/eqptSupC_SUP_A_Aplus.json (100%) rename tests/{ => checks}/sup_a_high_memory_check/eqptSupC_SUP_Aplus.json (100%) rename tests/{ => checks}/sup_a_high_memory_check/eqptSupC_no_SUP_A_Aplus.json (100%) rename tests/{ => checks}/sup_a_high_memory_check/test_sup_a_high_memory_check.py (87%) rename tests/{ => checks}/sup_hwrev_check/eqptSpCmnBlk_NEG.json (100%) rename tests/{ => checks}/sup_hwrev_check/eqptSpCmnBlk_POS.json (100%) rename tests/{ => checks}/sup_hwrev_check/test_sup_hwrev_check.py (85%) rename tests/{ => checks}/switch_bootflash_usage_check/eqptcapacityFSPartition.json (100%) rename tests/{ => checks}/switch_bootflash_usage_check/maintUpgJob_not_downloaded.json (100%) rename tests/{ => checks}/switch_bootflash_usage_check/maintUpgJob_old_ver_no_prop.json (100%) rename tests/{ => checks}/switch_bootflash_usage_check/maintUpgJob_pre_downloaded.json (100%) create mode 100644 tests/checks/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py rename tests/{ => checks}/telemetryStatsServerP_object_check/telemetryStatsServerP_neg.json (100%) rename tests/{ => checks}/telemetryStatsServerP_object_check/telemetryStatsServerP_pos.json (100%) rename tests/{ => checks}/telemetryStatsServerP_object_check/test_telemetryStatsServerP_object_check.py (86%) rename tests/{ => checks}/tep-to-tep_atomic_counter_check/dbgAcPath_max.json (100%) rename tests/{ => checks}/tep-to-tep_atomic_counter_check/dbgAcPath_na.json (100%) rename tests/{ => checks}/tep-to-tep_atomic_counter_check/dbgAcPath_pass.json (100%) rename tests/{ => checks}/tep-to-tep_atomic_counter_check/test_tep_to_tep_ac_count_check.py (71%) rename tests/{ => checks}/unsupported_fec_configuration_ex_check/test_unsupported_fec_configuration_ex_check.py (82%) rename tests/{ => checks}/unsupported_fec_configuration_ex_check/topSystem_neg.json (100%) rename tests/{ => checks}/unsupported_fec_configuration_ex_check/topSystem_pos.json (100%) rename tests/{ => checks}/uplink_limit_check/eqptPortP_NEG.json (100%) rename tests/{ => checks}/uplink_limit_check/eqptPortP_POS.json (100%) rename tests/{ => checks}/uplink_limit_check/eqptPortP_empty.json (100%) rename tests/{ => checks}/uplink_limit_check/test_uplink_limit_check.py (82%) rename tests/{ => checks}/validate_32_64_bit_image_check/firmwareFirmware_empty.json (100%) rename tests/{ => checks}/validate_32_64_bit_image_check/firmwareFirmware_neg.json (100%) rename tests/{ => checks}/validate_32_64_bit_image_check/firmwareFirmware_pos.json (100%) rename tests/{ => checks}/validate_32_64_bit_image_check/firmwareFirmware_pos2.json (100%) rename tests/{ => checks}/validate_32_64_bit_image_check/firmwareFirmware_pos3.json (100%) rename tests/{ => checks}/validate_32_64_bit_image_check/firmwareFirmware_pos4.json (100%) rename tests/{ => checks}/validate_32_64_bit_image_check/test_validate_32_64_bit_image_check.py (59%) rename tests/{ => checks}/vmm_active_uplinks_check/fvUplinkOrderCont_neg.json (100%) rename tests/{ => checks}/vmm_active_uplinks_check/fvUplinkOrderCont_not_exist.json (100%) rename tests/{ => checks}/vmm_active_uplinks_check/fvUplinkOrderCont_pos.json (100%) rename tests/{ => checks}/vmm_active_uplinks_check/test_vmm_active_uplinks_check.py (78%) rename tests/{ => checks}/vpc_paired_switches_check/test_vpc_paired_switches_check.py (78%) rename tests/{ => checks}/vpc_paired_switches_check/topSystem.json (100%) rename tests/{ => checks}/vzany_vzany_service_epg_check/test_vzany_vzany_service_epg_check.py (92%) rename tests/{ => checks}/vzany_vzany_service_epg_check/vzRsSubjGraphAtt.json (100%) rename tests/{ => checks}/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_cons_diff_VRFs.json (100%) rename tests/{ => checks}/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_only.json (100%) rename tests/{ => checks}/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny.json (100%) rename tests/{ => checks}/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny_2_VRFs.json (100%) delete mode 100644 tests/standby_sup_sync_check/test_standby_sup_sync_check.py delete mode 100644 tests/static_route_overlap_check/test_static_route_overlap_check.py delete mode 100644 tests/subnet_scope_check/test_subnet_scope_check.py delete mode 100644 tests/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py create mode 100644 tests/test_CheckManager.py create mode 100644 tests/test_ResultManager.py create mode 100644 tests/test_ThreadManager.py create mode 100644 tests/test_common_data.py delete mode 100644 tests/test_prepare.py delete mode 100644 tests/test_run_checks.py diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index b57821c7..923387d8 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # SPDX-License-Identifier: Apache-2.0 # # Copyright 2021 Cisco Systems, Inc. and its affiliates @@ -20,10 +21,11 @@ from six.moves import input from textwrap import TextWrapper from getpass import getpass -from collections import defaultdict +from collections import defaultdict, OrderedDict from datetime import datetime from argparse import ArgumentParser from itertools import chain +import threading import functools import shutil import warnings @@ -36,7 +38,8 @@ import os import re -SCRIPT_VERSION = "v3.2.0" +SCRIPT_VERSION = "v3.4.12" +DEFAULT_TIMEOUT = 600 # sec # result constants DONE = 'DONE' PASS = 'PASS' @@ -64,11 +67,11 @@ ts = datetime.now().strftime('%Y-%m-%dT%H-%M-%S') BUNDLE_NAME = 'preupgrade_validator_%s%s.tgz' % (ts, tz) DIR = 'preupgrade_validator_logs/' -JSON_DIR = DIR + 'json_results/' -META_FILE = DIR + 'meta.json' -RESULT_FILE = DIR + 'preupgrade_validator_%s%s.txt' % (ts, tz) -SUMMARY_FILE = DIR + 'summary.json' -LOG_FILE = DIR + 'preupgrade_validator_debug.log' +JSON_DIR = os.path.join(DIR, 'json_results/') +META_FILE = os.path.join(DIR, 'meta.json') +RESULT_FILE = os.path.join(DIR, 'preupgrade_validator_%s%s.txt' % (ts, tz)) +SUMMARY_FILE = os.path.join(DIR, 'summary.json') +LOG_FILE = os.path.join(DIR, 'preupgrade_validator_debug.log') warnings.simplefilter(action='ignore', category=FutureWarning) log = logging.getLogger() @@ -916,6 +919,286 @@ def is_firstver_gt_secondver(first_ver, second_ver): return result +class CustomThread(threading.Thread): + def __init__(self, *args, **kwargs): + super(CustomThread, self).__init__(*args, **kwargs) + self.exception = None + + def start(self, timeout=5.0): + """Thread.start() with timeout to wait for the thread start event. + + This method overrides `_started.wait()` at the end with a timeout to + prevent the issue explained below. + When MemoryError occurs during _start_new_thread(_bootstrap(), ()), + this method (start()) could get stuck forever at _started.wait(), which + is a threading.Event(), because _bootstrap() is supposed to trigger + _started.set() which may never happen because it appears some exceptions + are not raised to be captured by try/except in this method. + This was observed when the script was used inside a container with a + restricted memory allocation and resulted in the script to get stuck + and not being able to start remaining threads. + + Args: + timeout (float): How long we wait for the thread start event. + 5.0 sec by default. + """ + _active_limbo_lock = threading._active_limbo_lock + _limbo = threading._limbo + _start_new_thread = threading._start_new_thread + + # Python2 uses name mangling + if hasattr(self, "_Thread__initialized"): + self._initialized = self._Thread__initialized + if hasattr(self, "_Thread__started"): + self._started = self._Thread__started + if hasattr(self, "_Thread__bootstrap"): + self._bootstrap = self._Thread__bootstrap + + if not self._initialized: + raise RuntimeError("thread.__init__() not called") + + if self._started.is_set(): + raise RuntimeError("threads can only be started once") + + with _active_limbo_lock: + _limbo[self] = self + try: + _start_new_thread(self._bootstrap, ()) + except Exception: + with _active_limbo_lock: + del _limbo[self] + raise + self._started.wait(timeout) + # When self._started was not set within the time limit, handle it + # in the same way as when `_start_new_thread()` correctly captures + # the exception due to OOM. + if not self._started.is_set(): + with _active_limbo_lock: + del _limbo[self] + raise RuntimeError("can't start new thread") + + def run(self): + # Python2 uses name mangling + if hasattr(self, "_Thread__target"): + self._target = self._Thread__target + if hasattr(self, "_Thread__args"): + self._args = self._Thread__args + if hasattr(self, "_Thread__kwargs"): + self._kwargs = self._Thread__kwargs + + try: + if self._target is not None: + self._target(*self._args, **self._kwargs) + except Exception as e: + # Exceptions inside a thread should be captured in the thread, that + # is in the `check_wrapper`. + # If it's not caught inside the thread, notify the main thread. + self.exception = e + finally: + del self._target, self._args, self._kwargs + + +class ThreadManager: + """A class managing all threads to run individual checks. + + This class starts and monitors the status of all threads for check + functions decorated with check_wrapper(). This stops monitoring when all + threads completed or when timeout expired. + On a memory constrained setup, it may take time to start each thread. Some + thread/check may complete before other threads get started. To monitor the + progress correctly from the beginning, the monitoring is also done in a + thread while the main thread is starting all threads for each check. + """ + def __init__( + self, + funcs, + common_kwargs, + monitor_interval=0.5, # sec + monitor_timeout=600, # sec + callback_on_monitoring=None, + callback_on_start_failure=None, + callback_on_timeout=None, + ): + self.funcs = funcs + self.threads = None + self.common_kwargs = common_kwargs + + # Not using `thread.join(timeout)` because it waits for each thread sequentially, + # which means the program may wait for "timeout * num of threads" at worst case. + self.timeout_event = threading.Event() + self.monitor_interval = monitor_interval + self.monitor_timeout = monitor_timeout + self._monitor = self._generate_thread(target=self._monitor_progress) + + # Number of threads that were processed by `_start_thread()`, including + # both success and failure to start. + self._processed_threads_count = 0 + + # Custom callbacks + self._cb_on_monitoring = callback_on_monitoring + self._cb_on_start_failure = callback_on_start_failure + self._cb_on_start_failure_exception = None + self._cb_on_timeout = callback_on_timeout + + def start(self): + if self._monitor.is_alive(): + raise RuntimeError("Threading on going. Cannot start again.") + + self.threads = [ + self._generate_thread(target=func, kwargs=self.common_kwargs) + for func in self.funcs + ] + + self._monitor.start() + + for thread in self.threads: + self._start_thread(thread) + + def join(self): + self._monitor.join() + # If the thread had an exception that was not captured and handled correctly, + # re-raise it in the main thread to notify the script executor about it. + if self._monitor.exception: + raise self._monitor.exception + for thread in self.threads: + if thread.exception: + raise thread.exception + # Exception in the callback means failure to update the result as error. Need to + # re-raise it in the main thread to notify the script excutor about the risk of + # some check results left with in-progress forever. + if self._cb_on_start_failure_exception: + raise self._cb_on_start_failure_exception + + def is_timeout(self): + return self.timeout_event.is_set() + + def _generate_thread(self, target, args=(), kwargs=None): + if kwargs is None: + kwargs = {} + thread = CustomThread( + target=target, name=target.__name__, args=args, kwargs=kwargs + ) + thread.daemon = True + return thread + + def _start_thread(self, thread): + """ Start a thread. When failed due to OOM, retry again after an interval. + Until one of the following conditions are met, we don't move on. + - successfuly started the thread + - exceeded the queue timeout and gave up on this thread + - failed to start the thread for an unknown reason + """ + queue_timeout = 10 # sec + queue_interval = 1 # sec + time_elapsed = 0 # sec + thread_started = False + while not self.is_timeout(): + try: + log.info("({}) Starting thread.".format(thread.name)) + thread.start() + thread_started = True + break + except RuntimeError as e: + if str(e) != "can't start new thread": + log.error("({}) Unexpected error to start a thread.".format(thread.name), exc_info=True) + break + + log_msg = "({}) Not enough memory to start a new thread. ".format(thread.name) + if time_elapsed >= queue_timeout: + log.error(log_msg + "No queue time left. Give up.") + break + else: + log.info(log_msg + "Try again in {} second...".format(queue_interval)) + time.sleep(queue_interval) + time_elapsed += queue_interval + continue + except Exception: + log.error("({}) Unexpected error to start a thread.".format(thread.name), exc_info=True) + break + + # Custom cleanup callback for a thread that couldn't start. + if not thread_started and not thread.is_alive(): + log.error("({}) Failed to start thread.".format(thread.name)) + if self._cb_on_start_failure is not None: + try: + self._cb_on_start_failure(thread.name) + except Exception as e: + log.error("({}) Failed to update the result as error.".format(thread.name), exc_info=True) + self._cb_on_start_failure_exception = e + + self._processed_threads_count += 1 + + def _monitor_progress(self): + """Executed in a separate monitor thread""" + total = len(self.threads) + time_elapsed = 0 # sec + while True: + alive_count = sum(thread.is_alive() for thread in self.threads) + done = self._processed_threads_count - alive_count + + # Custom monitor callback + if self._cb_on_monitoring is not None: + self._cb_on_monitoring(done, total) + + if done == total: + break + + time.sleep(self.monitor_interval) + time_elapsed += self.monitor_interval + if time_elapsed > self.monitor_timeout: + log.error("Timeout. Stop monitoring threads.") + self.timeout_event.set() + break + + # Custom timeout callback per thread + if self.is_timeout() and self._cb_on_timeout is not None: + for thread in self.threads: + if thread.is_alive(): + self._cb_on_timeout(thread.name) + + +class ResultManager: + def __init__(self): + self.titles = {} # {check_id: check_title} + self.results = {} # {check_id: Result} + + def _update_aci_result(self, check_id, check_title, result_obj=None): + aci_result = AciResult(check_id, check_title, result_obj) + filepath = self.get_result_filepath(check_id) + write_jsonfile(filepath, aci_result.as_dict()) + return filepath + + def init_result(self, check_id, check_title): + self.titles[check_id] = check_title + filepath = self._update_aci_result(check_id, check_title) + log.info("Initialized in {}".format(filepath)) + + def update_result(self, check_id, result_obj): + check_title = self.titles.get(check_id) + if not check_title: + log.error("Check {} is not initialized. Failed to update.".format(check_id)) + return None + self.results[check_id] = result_obj + filepath = self._update_aci_result(check_id, check_title, result_obj) + log.info("Finalized result in {}".format(filepath)) + + def get_summary(self): + summary_headers = [PASS, FAIL_O, FAIL_UF, MANUAL, POST, NA, ERROR, 'TOTAL'] + summary = OrderedDict([(key, 0) for key in summary_headers]) + summary["TOTAL"] = len(self.results) + + for result in self.results.values(): + summary[result.result] += 1 + + return summary + + def get_result_filepath(self, check_id): + """filename should be only alphabet, number and underscore""" + filename = re.sub(r'[^a-zA-Z0-9_]+|\s+', '_', check_id) + '.json' + filepath = os.path.join(JSON_DIR, filename) + return filepath + + class AciResult: """ APIC uses an object called `syntheticMaintPValidate` to store the results of @@ -935,10 +1218,10 @@ class AciResult: PASS = "passed" FAIL = "failed" - def __init__(self, func_name, name, description): + def __init__(self, func_name, name, result_obj=None): self.ruleId = func_name self.name = name - self.description = description + self.description = "" self.reason = "" self.sub_reason = "" self.recommended_action = "" @@ -948,16 +1231,20 @@ def __init__(self, func_name, name, description): self.showValidation = True self.failureDetails = { "failType": "", + "header": [], "data": [], + "unformatted_header": [], "unformatted_data": [], } - - @property - def filename(self): - return re.sub(r'[^a-zA-Z0-9_]+|\s+', '_', self.ruleId) + '.json' + if result_obj: + self.update_with_results(result_obj) @staticmethod - def craftData(column, rows): + def convert_data(column, rows): + """Convert data from `Result` data format to `AciResult` data format. + Result - {header: [h1, h2,,,], data: [[d11, d21,,,], [d21, d22,,,],,,]} + AciResult - [{h1: d11, h2: d21,,,}, {h1: d21, h2: d22,,,},,,] + """ if not (isinstance(rows, list) and isinstance(column, list)): raise TypeError("Rows and column must be lists.") data = [] @@ -966,62 +1253,58 @@ def craftData(column, rows): r_len = len(rows[row_entry]) if r_len != c_len: raise ValueError("Row length ({}), data: {} does not match column length ({}).".format(r_len, rows[row_entry], c_len)) - entry = {} + entry = OrderedDict() for col_pos in range(c_len): entry[column[col_pos]] = str(rows[row_entry][col_pos]) data.append(entry) return data - def updateWithResults(self, result, recommended_action, msg, doc_url, headers, data, unformatted_headers, unformatted_data): - self.reason = msg - self.recommended_action = recommended_action - self.docUrl = doc_url + def update_with_results(self, result_obj): + self.recommended_action = result_obj.recommended_action + self.docUrl = result_obj.doc_url + + result = result_obj.result - # Show validation - if result in [NA, POST]: - self.showValidation = False + # Show validatio + self.showValidation = result not in (NA, POST) # Severity - if result in [FAIL_O, FAIL_UF]: - self.severity = "critical" - elif result in [ERROR]: - self.severity = "major" - elif result in [MANUAL]: - self.severity = "warning" - - self.ruleStatus = AciResult.PASS - if result not in [NA, PASS]: - self.ruleStatus = AciResult.FAIL - if not self.reason: - self.reason = "See Failure Details" + severity_map = {FAIL_O: "critical", FAIL_UF: "critical", ERROR: "major", MANUAL: "warning"} + self.severity = severity_map.get(result, "informational") + + # ruleStatus + self.ruleStatus = AciResult.PASS if result in (NA, PASS) else AciResult.FAIL + + # reason + self.reason = result_obj.msg + if not self.reason and self.ruleStatus == AciResult.FAIL: + self.reason = "See Failure Details." + + # failureDetails + if self.ruleStatus == AciResult.FAIL: self.failureDetails["failType"] = result - self.failureDetails["header"] = headers - self.failureDetails["data"] = self.craftData(headers, data) - if unformatted_headers and unformatted_data: - self.failureDetails["unformatted_data"] = self.craftData(unformatted_headers, unformatted_data) - if self.reason: - self.reason += "\n" - self.reason += ( - "Parse failure occurred, the provided data may not be complete. " - "Please contact Cisco TAC to identify the missing data." + self.failureDetails["header"] = result_obj.headers + self.failureDetails["data"] = self.convert_data(result_obj.headers, result_obj.data) + if result_obj.unformatted_headers and result_obj.unformatted_data: + self.failureDetails["unformatted_header"] = result_obj.unformatted_headers + self.failureDetails["unformatted_data"] = self.convert_data( + result_obj.unformatted_headers, result_obj.unformatted_data + ) + self.recommended_action += ( + "\n " + "Note that the provided data in the Failure Details is not complete" + " due to an issue in parsing data. Contact Cisco TAC for the full details." ) - def buildResult(self): + def as_dict(self): return {slot: getattr(self, slot) for slot in self.__slots__} - def writeResult(self, path=JSON_DIR): - if not os.path.isdir(path): - os.mkdir(path) - with open(os.path.join(path, self.filename), "w") as f: - json.dump(self.buildResult(), f, indent=2) - return "{}/{}".format(path, self.filename) - class Result: """Class to hold the result of a check.""" - __slots__ = ("result", "msg", "headers", "data", "unformatted_headers", "unformatted_data", "recommended_action", "doc_url", "adjust_title") + __slots__ = ("result", "msg", "headers", "data", "unformatted_headers", "unformatted_data", "recommended_action", "doc_url") - def __init__(self, result=PASS, msg="", headers=None, data=None, unformatted_headers=None, unformatted_data=None, recommended_action="", doc_url="", adjust_title=False): + def __init__(self, result=PASS, msg="", headers=None, data=None, unformatted_headers=None, unformatted_data=None, recommended_action="", doc_url=""): self.result = result self.msg = msg self.headers = headers if headers is not None else [] @@ -1030,52 +1313,54 @@ def __init__(self, result=PASS, msg="", headers=None, data=None, unformatted_hea self.unformatted_data = unformatted_data if unformatted_data is not None else [] self.recommended_action = recommended_action self.doc_url = doc_url - self.adjust_title = adjust_title def as_dict(self): return {slot: getattr(self, slot) for slot in self.__slots__} - def as_dict_for_json_result(self): - return {slot: getattr(self, slot) for slot in self.__slots__ if slot != "adjust_title"} - def check_wrapper(check_title): - """ - Decorator to wrap a check function to handle the printing of title and results, - and to write the results in a file in a JSON format. + """Decorator to wrap a check function with initializer and finalizer from `CheckManager`. + + The goal is for each check function to focus only on the check logic itself and return + `Result` object. The rest such as initializing the result, printing the result to stdout, + writing the result in a file in JSON etc. are handled through this wrapper and CheckManager. """ def decorator(check_func): @functools.wraps(check_func) - def wrapper(index, total_checks, *args, **kwargs): - # When init is True, we just initialize the result file and return - if kwargs.get("init") is True: - synth = AciResult(wrapper.__name__, check_title, "") - synth.writeResult() + def wrapper(*args, **kwargs): + # Initialization + initialize_check = kwargs.pop("initialize_check", None) + if initialize_check: + # Using `wrapper.__name__` instead of `check_func.__name` because + # both show the original check func name and `wrapper.__name__` can + # be dynamically changed inside each check func if needed. (mainly + # for test or debugging) + initialize_check(wrapper.__name__, check_title) return None + log.info("Start {}".format(wrapper.__name__)) + # Real Run (executed inside a thread) + # When `finalize_check` failed even in `except`, there is nothing we can + # do because it is usually because of system level issues like filesystem + # being full. In such a case, we cannot even update the result of the check + # as `failed` from `in-progress`. To inform the script executor and prevent it + # from indefinitely waiting, we let the exception to go up to the top (`main()`) + # and abort the script immediately. + finalize_check = kwargs.pop("finalize_check") try: - # Print `[Check 1/81] ...` - print_title(check_title, index, total_checks) - - # Run check, expecting it to return a `Result` object r = check_func(*args, **kwargs) - - # Print `[Check 1/81] <title>... <msg> <result>\n<failure details>` - print_result(title=check_title, **r.as_dict()) + finalize_check(wrapper.__name__, r) + except MemoryError: + msg = "Not enough memory to complete this check." + r = Result(result=ERROR, msg=msg) + log.error(msg, exc_info=True) + finalize_check(wrapper.__name__, r) except Exception as e: - log.exception(e) - r = Result(result=ERROR, msg='Unexpected Error: {}'.format(e)) - print_result(title=check_title, **r.as_dict()) - finally: - # Write results in JSON - # Using `wrapper.__name__` instead of `check_func.__name` because - # both show the original check func name and `wrapper.__name__` can - # be dynamically changed inside each check func if needed. (mainly - # for test or debugging) - synth = AciResult(wrapper.__name__, check_title, "") - synth.updateWithResults(**r.as_dict_for_json_result()) - synth.writeResult() - return r.result + msg = "Unexpected Error: {}".format(e) + r = Result(result=ERROR, msg=msg) + log.error(msg, exc_info=True) + finalize_check(wrapper.__name__, r) + return r return wrapper return decorator @@ -1201,38 +1486,40 @@ def get_row(widths, values, spad=" ", lpad=""): def prints(objects, sep=' ', end='\n'): with open(RESULT_FILE, 'a') as f: print(objects, sep=sep, end=end, file=sys.stdout) + if end == "\r": + end = "\n" # easier to read with \n in a log file print(objects, sep=sep, end=end, file=f) sys.stdout.flush() f.flush() -def print_title(title, index=None, total=None): - if index and total: - prints('[Check{:3}/{}] {}... '.format(index, total, title), end='') +def print_progress(done, total, bar_length=100): + if not total: + progress = 1.0 else: - prints('{:14}{}... '.format('', title), end='') + progress = done / float(total) + filled = int(bar_length * progress) + bar = "â–ˆ" * filled + "-" * (bar_length - filled) + prints("Progress: |{}| {}/{} checks completed".format(bar, done, total), end="\r") -def print_result(title, result, msg='', +def print_result(index, total, title, + result, msg='', headers=None, data=None, unformatted_headers=None, unformatted_data=None, recommended_action='', - doc_url='', - adjust_title=False): - FULL_LEN = 138 # length of `[Check XX/YY] <title>... <msg> --padding-- <RESULT>` - CHECK_LEN = 18 # length of `[Check XX/YY] ... ` - padding = FULL_LEN - CHECK_LEN - len(title) - len(msg) - if adjust_title: - # adjust padding when the result is on the second line. - # 1st: `[Check XX/YY] <title>... ` - # 2nd: ` <msg> --padding-- <RESULT>` - padding += len(title) + CHECK_LEN + doc_url=''): + """Print `[Check XX/YY] <title>... <msg> --padding-- <result>` + some data""" + idx_len = len(str(total)) + 1 + output = "[Check{:{}}/{}] {}... {}".format(index, idx_len, total, title, msg) + FULL_LEN = 138 # length of `[Check XX/YY] <title>... <msg> --padding-- <result>` + padding = FULL_LEN - len(output) if padding < len(result): # when `msg` is too long (ex. unknown exception), `padding` may get shorter # than what it's padding (`result`), or worse, may get negative. # In such a case, keep one whitespace padding even if the full length gets longer. padding = len(result) + 1 - output = '{}{:>{}}'.format(msg, result, padding) + output += "{:>{}}".format(result, padding) if data: data.sort() output += '\n' + format_table(headers, data) @@ -1249,6 +1536,27 @@ def print_result(title, result, msg='', prints(output) +def write_jsonfile(filepath, content): + with open(filepath, 'w') as f: + json.dump(content, f, indent=2) + + +def write_script_metadata(api_only, timeout, total_checks, common_data): + metadata = { + "name": "PreupgradeCheck", + "method": "standalone script", + "datetime": ts + tz, + "script_version": str(SCRIPT_VERSION), + "cversion": str(common_data["cversion"]), + "tversion": str(common_data["tversion"]), + "sw_cversion": str(common_data["sw_cversion"]), + "api_only": api_only, + "timeout": timeout, + "total_checks": total_checks, + } + write_jsonfile(META_FILE, metadata) + + def _icurl_error_handler(imdata): if imdata and "error" in imdata[0]: if "not found in class" in imdata[0]['error']['attributes']['text']: @@ -1295,7 +1603,7 @@ def run_cmd(cmd, splitlines=True): """ Run a shell command. :param cmd: Command to run, can be a string or a list. - :param splitlines: If True, splits the output into a list of lines. + :param splitlines: If True, splits the output into a list of lines. If False, returns the raw text output as a single string. Returns the output of the command. """ @@ -1428,6 +1736,32 @@ def get_switch_version(): return None +def query_common_data(api_only=False, arg_cversion=None, arg_tversion=None): + username = password = None + if not api_only: + username, password = get_credentials() + + try: + cversion = get_current_version(arg_cversion) + tversion = get_target_version(arg_tversion) + sw_cversion = get_switch_version() + vpc_nodes = get_vpc_nodes() + except Exception as e: + prints('\n\nError: %s' % e) + prints("Initial query failed. Ensure APICs are healthy. Ending script run.") + log.exception(e) + sys.exit(1) + + return { + 'username': username, + 'password': password, + 'cversion': cversion, + 'tversion': tversion, + 'sw_cversion': sw_cversion, + 'vpc_node_ids': vpc_nodes, + } + + @check_wrapper(check_title="APIC Cluster Status") def apic_cluster_health_check(cversion, **kwargs): result = FAIL_UF @@ -2253,14 +2587,12 @@ def apic_ssd_check(cversion, username, password, **kwargs): has_error = False dn_regex = node_regex + r'/.+p-\[(?P<storage>.+)\]-f' faultInsts = icurl('class', 'faultInst.json?query-target-filter=eq(faultInst.code,"F2731")') - adjust_title = False if len(faultInsts) == 0 and (cversion.older_than("4.2(7f)") or cversion.older_than("5.2(1g)")): controller = icurl('class', 'topSystem.json?query-target-filter=eq(topSystem.role,"controller")') if not controller: return Result(result=ERROR, msg="topSystem response empty. Is the cluster healthy?", doc_url=doc_url) print('') - adjust_title = True report_other = False checked_apics = {} for apic in controller: @@ -2269,8 +2601,6 @@ def apic_ssd_check(cversion, username, password, **kwargs): checked_apics[attr['address']] = 1 pod_id = attr['podId'] node_id = attr['id'] - node_title = 'Checking %s...' % attr['name'] - print_title(node_title) try: c = Connection(attr['address']) c.username = username @@ -2279,7 +2609,6 @@ def apic_ssd_check(cversion, username, password, **kwargs): c.connect() except Exception as e: data.append([attr['id'], attr['name'], '-', '-', str(e)]) - print_result(node_title, ERROR) has_error = True continue try: @@ -2287,7 +2616,6 @@ def apic_ssd_check(cversion, username, password, **kwargs): 'grep -oE "SSD Wearout Indicator is [0-9]+" /var/log/dme/log/svc_ifc_ae.bin.log | tail -1') except Exception as e: data.append([attr['id'], attr['name'], '-', '-', str(e)]) - print_result(node_title, ERROR) has_error = True continue @@ -2297,11 +2625,9 @@ def apic_ssd_check(cversion, username, password, **kwargs): if int(wearout) < 5: data.append([pod_id, node_id, "Solid State Disk", wearout, recommended_action]) report_other = True - print_result(node_title, DONE) continue if report_other: data.append([pod_id, node_id, "Solid State Disk", wearout, "No Action Required"]) - print_result(node_title, DONE) else: headers = ["Fault", "Pod", "Node", "Storage Unit", "% lifetime remaining", "Recommended Action"] for faultInst in faultInsts: @@ -2324,7 +2650,6 @@ def apic_ssd_check(cversion, username, password, **kwargs): unformatted_headers=unformatted_headers, unformatted_data=unformatted_data, doc_url=doc_url, - adjust_title=adjust_title, ) @@ -2922,14 +3247,11 @@ def apic_version_md5_check(tversion, username, password, **kwargs): md5_names = [] has_error = False - prints('') nodes_response_json = icurl('class', 'topSystem.json') for node in nodes_response_json: if node['topSystem']['attributes']['role'] != "controller": continue apic_name = node['topSystem']['attributes']['name'] - node_title = 'Checking %s...' % apic_name - print_title(node_title) try: c = Connection(node['topSystem']['attributes']['address']) c.username = username @@ -2938,7 +3260,6 @@ def apic_version_md5_check(tversion, username, password, **kwargs): c.connect() except Exception as e: data.append([apic_name, '-', '-', str(e)]) - print_result(node_title, ERROR) has_error = True continue @@ -2948,12 +3269,10 @@ def apic_version_md5_check(tversion, username, password, **kwargs): except Exception as e: data.append([apic_name, '-', '-', 'ls command via ssh failed due to:{}'.format(str(e))]) - print_result(node_title, ERROR) has_error = True continue if "No such file or directory" in c.output: data.append([apic_name, str(tversion), '-', 'image not found']) - print_result(node_title, FAIL_UF) continue try: @@ -2962,12 +3281,10 @@ def apic_version_md5_check(tversion, username, password, **kwargs): except Exception as e: data.append([apic_name, str(tversion), '-', 'failed to check md5sum via ssh due to:{}'.format(str(e))]) - print_result(node_title, ERROR) has_error = True continue if "No such file or directory" in c.output: data.append([apic_name, str(tversion), '-', 'md5sum file not found']) - print_result(node_title, FAIL_UF) continue for line in c.output.split("\n"): words = line.split() @@ -2980,11 +3297,9 @@ def apic_version_md5_check(tversion, username, password, **kwargs): break else: data.append([apic_name, str(tversion), '-', 'unexpected output when checking md5sum file']) - print_result(node_title, ERROR) has_error = True continue - print_result(node_title, DONE) if len(set(md5s)) > 1: for name, md5 in zip(md5_names, md5s): data.append([name, str(tversion), md5, 'md5sum do not match on all APICs']) @@ -2992,7 +3307,7 @@ def apic_version_md5_check(tversion, username, password, **kwargs): result = ERROR elif not data: result = PASS - return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url, adjust_title=True) + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) # Connection Based Check @@ -5349,11 +5664,8 @@ def observer_db_size_check(username, password, **kwargs): return Result(result=ERROR, msg='topSystem response empty. Is the cluster healthy?') has_error = False - prints('') for apic in controllers: attr = apic['topSystem']['attributes'] - node_title = 'Checking %s...' % attr['name'] - print_title(node_title) try: c = Connection(attr['address']) c.username = username @@ -5362,7 +5674,6 @@ def observer_db_size_check(username, password, **kwargs): c.connect() except Exception as e: data.append([attr['id'], attr['name'], str(e)]) - print_result(node_title, ERROR) has_error = True continue try: @@ -5370,7 +5681,6 @@ def observer_db_size_check(username, password, **kwargs): c.cmd(cmd) if "No such file or directory" in c.output: data.append([attr['id'], '/data2/dbstats/ not found', "Check user permissions or retry as 'apic#fallback\\\\admin'"]) - print_result(node_title, ERROR) has_error = True continue dbstats = c.output.split("\n") @@ -5381,17 +5691,15 @@ def observer_db_size_check(username, password, **kwargs): file_size = size_match.group("size") file_name = "/data2/dbstats/" + size_match.group("file") data.append([attr['id'], file_name, file_size]) - print_result(node_title, DONE) except Exception as e: data.append([attr['id'], attr['name'], str(e)]) - print_result(node_title, ERROR) has_error = True continue if has_error: result = ERROR elif data: result = FAIL_UF - return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url, adjust_title=True) + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) @check_wrapper(check_title='AVE End-of-Life') @@ -5539,7 +5847,7 @@ def configpush_shard_check(tversion, **kwargs): doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#policydist-configpushshardcont-crash' if not tversion: - return Result(result=MANUAL, msg=TVER_MISSING) + return Result(result=MANUAL, msg=TVER_MISSING) if tversion.older_than("6.1(4a)"): result = PASS @@ -5570,11 +5878,12 @@ def parse_args(args): parser.add_argument("-n", "--no-cleanup", action="store_true", help="Skip all file cleanup after script execution.") parser.add_argument("-v", "--version", action="store_true", help="Only show the script version, then end.") parser.add_argument("--total-checks", action="store_true", help="Only show the total number of checks, then end.") + parser.add_argument("--timeout", action="store", nargs="?", type=int, const=-1, default=DEFAULT_TIMEOUT, help="Show default script timeout (sec) or overwrite it when a number is provided (e.g. --timeout 1200).") parsed_args = parser.parse_args(args) return parsed_args -def initialize(): +def init_system(): """ Initialize the script environment, create necessary directories and set up log. Not required for some options such as `--version` or `--total-checks`. @@ -5585,51 +5894,49 @@ def initialize(): log.info("Creating directories %s and %s", DIR, JSON_DIR) os.mkdir(DIR) os.mkdir(JSON_DIR) - fmt = '[%(asctime)s.%(msecs)03d{} %(levelname)-8s %(funcName)20s:%(lineno)-4d] %(message)s'.format(tz) + fmt = '[%(asctime)s.%(msecs)03d{} %(levelname)-8s %(funcName)s:%(lineno)-4d(%(threadName)s)] %(message)s'.format(tz) logging.basicConfig(level=logging.DEBUG, filename=LOG_FILE, format=fmt, datefmt='%Y-%m-%d %H:%M:%S') -def prepare(api_only, arg_tversion, arg_cversion, checks): - prints(' ==== %s%s, Script Version %s ====\n' % (ts, tz, SCRIPT_VERSION)) - prints('!!!! Check https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script for Latest Release !!!!\n') +def wrapup_system(no_cleanup): + subprocess.check_output(['tar', '-czf', BUNDLE_NAME, DIR]) + bundle_loc = '/'.join([os.getcwd(), BUNDLE_NAME]) + prints(""" + Pre-Upgrade Check Complete. + Next Steps: Address all checks flagged as FAIL, ERROR or MANUAL CHECK REQUIRED - # Create empty result files for all checks - for idx, check in enumerate(checks): - check(idx + 1, len(checks), init=True) + Result output and debug info saved to below bundle for later reference. + Attach this bundle to Cisco TAC SRs opened to address the flagged checks. - username = password = None - if not api_only: - username, password = get_credentials() - try: - cversion = get_current_version(arg_cversion) - tversion = get_target_version(arg_tversion) - vpc_nodes = get_vpc_nodes() - sw_cversion = get_switch_version() - except Exception as e: - prints('\n\nError: %s' % e) - prints("Initial query failed. Ensure APICs are healthy. Ending script run.") - log.exception(e) - sys.exit() - inputs = {'username': username, 'password': password, - 'cversion': cversion, 'tversion': tversion, - 'vpc_node_ids': vpc_nodes, 'sw_cversion': sw_cversion} - metadata = { - "name": "PreupgradeCheck", - "method": "standalone script", - "datetime": ts + tz, - "script_version": str(SCRIPT_VERSION), - "cversion": str(cversion), - "tversion": str(tversion), - "sw_cversion": str(sw_cversion), - "api_only": api_only, - "total_checks": len(checks), - } - with open(META_FILE, "w") as f: - json.dump(metadata, f, indent=2) - return inputs + Result Bundle: {bundle} +""".format(bundle=bundle_loc)) + prints('==== Script Version %s FIN ====' % (SCRIPT_VERSION)) + + # puv integration needs to keep reading files from `JSON_DIR` under `DIR`. + if not no_cleanup and os.path.isdir(DIR): + log.info('Cleaning up temporary files and directories...') + shutil.rmtree(DIR) -def get_checks(api_only, debug_function): +class CheckManager: + """Central managing point of all checks. + Highlevel flows: + 1. Initialize checks + Through `intialize_check()` in the decorator `check_wrapper` for + each check, this does two things: + 1. get the mapping of check_title to check_id which is a check function name. + 2. write empty `AciResult` of each check into a JSON result file. + which is automatically done via decorator `check_wrapper`. + 2. Run checks in thread + Monitor the progress with timeout + 3. Finalize check results + When checks completed within the time limit (`self.monitor_timeout`), + `finalize_check()` is called through `check_wrapper`. + This does two things: + 1. get the mapping of `Result` to check_id + 2. update the JSON result file with the new `AciResult` + 4. Print the result to stdout + """ api_checks = [ # General Checks target_version_compatibility_check, @@ -5721,10 +6028,9 @@ def get_checks(api_only, debug_function): configpush_shard_check, ] - conn_checks = [ + ssh_checks = [ # General apic_version_md5_check, - apic_database_size_check, # Faults standby_apic_disk_space_check, @@ -5732,62 +6038,97 @@ def get_checks(api_only, debug_function): # Bugs observer_db_size_check, - apic_ca_cert_validation, - ] - if debug_function: - return [check for check in api_checks + conn_checks if check.__name__ == debug_function] - if api_only: - return api_checks - return conn_checks + api_checks - - -def run_checks(checks, inputs): - summary_headers = [PASS, FAIL_O, FAIL_UF, MANUAL, POST, NA, ERROR, 'TOTAL'] - summary = {key: 0 if key != 'TOTAL' else len(checks) for key in summary_headers} - for idx, check in enumerate(checks): - try: - r = check(idx + 1, len(checks), **inputs) - summary[r] += 1 - except KeyboardInterrupt: - prints('\n\n!!! KeyboardInterrupt !!!\n') - break - except Exception as e: - prints('') - err = 'Wrapper Error: %s' % e - print_title(err) - print_result(title=err, result=ERROR) - summary[ERROR] += 1 - logging.exception(e) + cli_checks = [ + # General + apic_database_size_check, - prints('\n=== Summary Result ===\n') - res = max(summary_headers, key=len) - max_header_len = len(res) - for key in summary_headers: - prints('{:{}} : {:2}'.format(key, max_header_len, summary[key])) + # Bugs + apic_ca_cert_validation, + ] - with open(SUMMARY_FILE, 'w') as f: - json.dump(summary, f, indent=2) + def __init__(self, api_only=False, debug_function="", timeout=600, monitor_interval=0.5): + self.api_only = api_only + self.debug_function = debug_function + self.monitor_interval = monitor_interval # sec + self.monitor_timeout = timeout # sec + self.timeout_event = None + self.check_funcs = self.get_check_funcs() -def wrapup(no_cleanup): - subprocess.check_output(['tar', '-czf', BUNDLE_NAME, DIR]) - bundle_loc = '/'.join([os.getcwd(), BUNDLE_NAME]) - prints(""" - Pre-Upgrade Check Complete. - Next Steps: Address all checks flagged as FAIL, ERROR or MANUAL CHECK REQUIRED + self.rm = ResultManager() - Result output and debug info saved to below bundle for later reference. - Attach this bundle to Cisco TAC SRs opened to address the flagged checks. - - Result Bundle: {bundle} - """.format(bundle=bundle_loc)) - prints('==== Script Version %s FIN ====' % (SCRIPT_VERSION)) + @property + def total_checks(self): + return len(self.check_funcs) - # puv integration needs to keep reading files from `JSON_DIR` under `DIR`. - if not no_cleanup and os.path.isdir(DIR): - log.info('Cleaning up temporary files and directories...') - shutil.rmtree(DIR) + @property + def check_ids(self): + return [check_func.__name__ for check_func in self.check_funcs] + + def get_check_funcs(self): + all_checks = [] + self.api_checks # must be a new list to avoid changing api_checks + if not self.api_only: + all_checks += self.ssh_checks + self.cli_checks + if self.debug_function: + return [check for check in all_checks if check.__name__ == self.debug_function] + return all_checks + + def get_check_title(self, check_id): + title = self.rm.titles.get(check_id, "") + if not title: + log.error("Failed to find title for {}".format(check_id)) + return title + + def get_check_result(self, check_id): + result_obj = self.rm.results.get(check_id) + if not result_obj: + log.error("Failed to find result for {}".format(check_id)) + return result_obj + + def get_result_summary(self): + return self.rm.get_summary() + + def initialize_check(self, check_id, check_title): + self.rm.init_result(check_id, check_title) + + def finalize_check(self, check_id, result_obj): + # We do not update the result from here in the case of timeout. + if self.timeout_event and self.timeout_event.is_set(): + return None + if not isinstance(result_obj, Result): + raise TypeError("The result of {} is not a `Result` object".format(check_id)) + return None + self.rm.update_result(check_id, result_obj) + + def finalize_check_on_thread_failure(self, check_id): + """Update the result of a check that couldn't start as ERROR""" + r = Result(result=ERROR, msg="Skipped due to a failure in starting a thread for this check.") + self.rm.update_result(check_id, r) + + def finalize_check_on_thread_timeout(self, check_id): + """Update the result of a check that couldn't finish in time as ERROR""" + msg = "Timeout. Unable to finish in time ({} sec).".format(self.monitor_timeout) + r = Result(result=ERROR, msg=msg) + self.rm.update_result(check_id, r) + + def initialize_checks(self): + for check_func in self.check_funcs: + check_func(initialize_check=self.initialize_check) + + def run_checks(self, common_data): + tm = ThreadManager( + funcs=self.check_funcs, + common_kwargs=dict({"finalize_check": self.finalize_check}, **common_data), + monitor_interval=self.monitor_interval, + monitor_timeout=self.monitor_timeout, + callback_on_monitoring=print_progress, + callback_on_start_failure=self.finalize_check_on_thread_failure, + callback_on_timeout=self.finalize_check_on_thread_timeout, + ) + self.timeout_event = tm.timeout_event + tm.start() + tm.join() def main(_args=None): @@ -5795,16 +6136,63 @@ def main(_args=None): if args.version: print(SCRIPT_VERSION) return - checks = get_checks(args.api_only, args.debug_function) + + if args.timeout == -1: + print("Timeout(sec): {}".format(DEFAULT_TIMEOUT)) + return + + cm = CheckManager(args.api_only, args.debug_function, args.timeout) + if args.total_checks: - print("Total Number of Checks: {}".format(len(checks))) + print("Total Number of Checks: {}".format(cm.total_checks)) return - initialize() - inputs = prepare(args.api_only, args.tversion, args.cversion, checks) - run_checks(checks, inputs) - wrapup(args.no_cleanup) + init_system() + + # Initialize checks with empty results + cm.initialize_checks() + + prints(' ==== %s%s, Script Version %s ====\n' % (ts, tz, SCRIPT_VERSION)) + prints('!!!! Check https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script for Latest Release !!!!\n') + + common_data = query_common_data(args.api_only, args.cversion, args.tversion) + write_script_metadata(args.api_only, args.timeout, cm.total_checks, common_data) + + cm.run_checks(common_data) + + # Print result reports + prints("\n") + if cm.timeout_event.is_set(): + prints("Timeout !!! Abort and printing the results...\n") + + prints("\n=== Check Result (failed only) ===\n") + + # Print result of each failed check + for index, check_id in enumerate(cm.check_ids): + result_obj = cm.get_check_result(check_id) + if not result_obj or result_obj.result in (NA, PASS): + continue + check_title = cm.get_check_title(check_id) + print_result(index + 1, cm.total_checks, check_title, **result_obj.as_dict()) + + # Print summary + summary = cm.get_result_summary() + prints('\n=== Summary Result ===\n') + longest_header = max(summary.keys(), key=len) + max_header_len = len(longest_header) + for key in summary: + prints('{:{}} : {:2}'.format(key, max_header_len, summary[key])) + + write_jsonfile(SUMMARY_FILE, summary) + + wrapup_system(args.no_cleanup) if __name__ == "__main__": - main() + try: + main() + except Exception as e: + msg = "Abort due to unexpected error - {}".format(e) + prints(msg) + log.error(msg, exc_info=True) + sys.exit(1) diff --git a/pytest.ini b/pytest.ini index 015777f4..aac0d267 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] log_cli = true log_cli_level = DEBUG -log_cli_format = [%(asctime)s.%(msecs)03d %(levelname)-8s %(funcName)20s:%(lineno)-4d] %(message)s +log_cli_format = [%(asctime)s.%(msecs)03d %(levelname)-8s %(funcName)s:%(lineno)-4d(%(threadName)s)] %(message)s diff --git a/tests/access_untagged_check/faultInst_NEG.json b/tests/checks/access_untagged_check/faultInst_NEG.json similarity index 100% rename from tests/access_untagged_check/faultInst_NEG.json rename to tests/checks/access_untagged_check/faultInst_NEG.json diff --git a/tests/access_untagged_check/faultInst_POS.json b/tests/checks/access_untagged_check/faultInst_POS.json similarity index 100% rename from tests/access_untagged_check/faultInst_POS.json rename to tests/checks/access_untagged_check/faultInst_POS.json diff --git a/tests/access_untagged_check/test_access_untagged_check.py b/tests/checks/access_untagged_check/test_access_untagged_check.py similarity index 80% rename from tests/access_untagged_check/test_access_untagged_check.py rename to tests/checks/access_untagged_check/test_access_untagged_check.py index 21851c71..f7a6d610 100644 --- a/tests/access_untagged_check/test_access_untagged_check.py +++ b/tests/checks/access_untagged_check/test_access_untagged_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "access_untagged_check" # icurl queries faultInsts = 'faultInst.json?&query-target-filter=wcard(faultInst.changeSet,"native-or-untagged-encap-failure")' @@ -27,6 +28,6 @@ ) ], ) -def test_logic(mock_icurl,expected_result): - result = script.access_untagged_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/aes_encryption_check/exportcryptkey.json b/tests/checks/aes_encryption_check/exportcryptkey.json similarity index 100% rename from tests/aes_encryption_check/exportcryptkey.json rename to tests/checks/aes_encryption_check/exportcryptkey.json diff --git a/tests/aes_encryption_check/exportcryptkey_disabled.json b/tests/checks/aes_encryption_check/exportcryptkey_disabled.json similarity index 100% rename from tests/aes_encryption_check/exportcryptkey_disabled.json rename to tests/checks/aes_encryption_check/exportcryptkey_disabled.json diff --git a/tests/aes_encryption_check/test_aes_encryption_check.py b/tests/checks/aes_encryption_check/test_aes_encryption_check.py similarity index 87% rename from tests/aes_encryption_check/test_aes_encryption_check.py rename to tests/checks/aes_encryption_check/test_aes_encryption_check.py index acd6e064..440a8603 100644 --- a/tests/aes_encryption_check/test_aes_encryption_check.py +++ b/tests/checks/aes_encryption_check/test_aes_encryption_check.py @@ -9,6 +9,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "aes_encryption_check" + # icurl queries exportcryptkey = "uni/exportcryptkey.json" @@ -55,6 +57,6 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.aes_encryption_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/apic_ca_cert_validation/NEG_certreq.txt b/tests/checks/apic_ca_cert_validation/NEG_certreq.txt similarity index 100% rename from tests/apic_ca_cert_validation/NEG_certreq.txt rename to tests/checks/apic_ca_cert_validation/NEG_certreq.txt diff --git a/tests/apic_ca_cert_validation/POS_certreq.txt b/tests/checks/apic_ca_cert_validation/POS_certreq.txt similarity index 100% rename from tests/apic_ca_cert_validation/POS_certreq.txt rename to tests/checks/apic_ca_cert_validation/POS_certreq.txt diff --git a/tests/apic_ca_cert_validation/test_apic_ca_cert_validation.py b/tests/checks/apic_ca_cert_validation/test_apic_ca_cert_validation.py similarity index 69% rename from tests/apic_ca_cert_validation/test_apic_ca_cert_validation.py rename to tests/checks/apic_ca_cert_validation/test_apic_ca_cert_validation.py index d76d9555..338a66c4 100644 --- a/tests/apic_ca_cert_validation/test_apic_ca_cert_validation.py +++ b/tests/checks/apic_ca_cert_validation/test_apic_ca_cert_validation.py @@ -8,6 +8,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "apic_ca_cert_validation" + @pytest.mark.parametrize( "certreq_out_file, expected_result", @@ -24,9 +26,9 @@ ), ], ) -def test_logic(certreq_out_file, expected_result): - data_path = os.path.join("tests", dir, certreq_out_file) +def test_logic(run_check, certreq_out_file, expected_result): + data_path = os.path.join("tests", "checks", dir, certreq_out_file) with open(data_path, "r") as file: certreq_out = file.read() - result = script.apic_ca_cert_validation(1, 1, certreq_out=certreq_out) - assert result == expected_result + result = run_check(certreq_out=certreq_out) + assert result.result == expected_result diff --git a/tests/apic_database_size_check/infraWiNode_3.json b/tests/checks/apic_database_size_check/infraWiNode_3.json similarity index 100% rename from tests/apic_database_size_check/infraWiNode_3.json rename to tests/checks/apic_database_size_check/infraWiNode_3.json diff --git a/tests/apic_database_size_check/infraWiNode_4.json b/tests/checks/apic_database_size_check/infraWiNode_4.json similarity index 100% rename from tests/apic_database_size_check/infraWiNode_4.json rename to tests/checks/apic_database_size_check/infraWiNode_4.json diff --git a/tests/apic_database_size_check/test_apic_database_size_check.py b/tests/checks/apic_database_size_check/test_apic_database_size_check.py similarity index 96% rename from tests/apic_database_size_check/test_apic_database_size_check.py rename to tests/checks/apic_database_size_check/test_apic_database_size_check.py index 73eb62ca..8a651bc7 100644 --- a/tests/apic_database_size_check/test_apic_database_size_check.py +++ b/tests/checks/apic_database_size_check/test_apic_database_size_check.py @@ -2,7 +2,6 @@ import pytest import logging import importlib -import json from helpers.utils import read_data script = importlib.import_module("aci-preupgrade-validation-script") @@ -10,6 +9,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "apic_database_size_check" + apic_node_api = 'infraWiNode.json' apic1_pm_cat = "cat /debug/apic1/policymgr/mitmocounters/mo | grep -v ALL | sort -rn -k3" @@ -166,6 +167,7 @@ ] }""" + @pytest.mark.parametrize( "icurl_outputs, cmd_outputs, cversion, expected_result", [ @@ -292,10 +294,11 @@ ), ], ) -def test_logic(mock_icurl, mock_run_cmd, cversion, expected_result): - cver = script.AciVersion(cversion) if cversion else None - result = script.apic_database_size_check(1, 1, cver) - assert result == expected_result +def test_logic(run_check, mock_icurl, mock_run_cmd, cversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion) if cversion else None + ) + assert result.result == expected_result @pytest.mark.parametrize( @@ -326,7 +329,8 @@ def test_logic(mock_icurl, mock_run_cmd, cversion, expected_result): ), ], ) -def test_permission_logic(mock_icurl, mock_run_cmd, cversion, expected_result): - cver = script.AciVersion(cversion) if cversion else None - result = script.apic_database_size_check(1, 1, cver) - assert result == expected_result +def test_permission_logic(run_check, mock_icurl, mock_run_cmd, cversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion) if cversion else None + ) + assert result.result == expected_result diff --git a/tests/apic_version_md5_check/firmwareFirmware_6.0.5h.json b/tests/checks/apic_version_md5_check/firmwareFirmware_6.0.5h.json similarity index 100% rename from tests/apic_version_md5_check/firmwareFirmware_6.0.5h.json rename to tests/checks/apic_version_md5_check/firmwareFirmware_6.0.5h.json diff --git a/tests/apic_version_md5_check/firmwareFirmware_6.0.5h_image_sign_fail.json b/tests/checks/apic_version_md5_check/firmwareFirmware_6.0.5h_image_sign_fail.json similarity index 100% rename from tests/apic_version_md5_check/firmwareFirmware_6.0.5h_image_sign_fail.json rename to tests/checks/apic_version_md5_check/firmwareFirmware_6.0.5h_image_sign_fail.json diff --git a/tests/apic_version_md5_check/test_apic_version_md5_check.py b/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py similarity index 96% rename from tests/apic_version_md5_check/test_apic_version_md5_check.py rename to tests/checks/apic_version_md5_check/test_apic_version_md5_check.py index 19ea2630..7b3e2470 100644 --- a/tests/apic_version_md5_check/test_apic_version_md5_check.py +++ b/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py @@ -9,6 +9,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "apic_version_md5_check" + api_firmware = "fwrepo/fw-aci-apic-dk9.6.0.5h.json" api_topSystem = "topSystem.json" @@ -265,7 +267,10 @@ ), ], ) -def test_logic(mock_icurl, mock_conn, tversion, expected_result): - tver = script.AciVersion(tversion) if tversion else None - result = script.apic_version_md5_check(1, 1, tver, "fake_username", "fake_password") - assert result == expected_result +def test_logic(run_check, mock_icurl, mock_conn, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, + username="fake_username", + password="fake_password", + ) + assert result.result == expected_result diff --git a/tests/apic_version_md5_check/topSystem.json b/tests/checks/apic_version_md5_check/topSystem.json similarity index 100% rename from tests/apic_version_md5_check/topSystem.json rename to tests/checks/apic_version_md5_check/topSystem.json diff --git a/tests/ave_eol_check/test_ave_eol_check.py b/tests/checks/ave_eol_check/test_ave_eol_check.py similarity index 81% rename from tests/ave_eol_check/test_ave_eol_check.py rename to tests/checks/ave_eol_check/test_ave_eol_check.py index 38db4384..0bf67c06 100644 --- a/tests/ave_eol_check/test_ave_eol_check.py +++ b/tests/checks/ave_eol_check/test_ave_eol_check.py @@ -9,12 +9,13 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "ave_eol_check" # icurl queries - ave_api = 'vmmDomP.json' ave_api += '?query-target-filter=eq(vmmDomP.enableAVE,"true")' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ @@ -44,6 +45,8 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.ave_eol_check(1, 1, script.AciVersion(tversion) if tversion else None) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, + ) + assert result.result == expected_result diff --git a/tests/ave_eol_check/vmmDomP_POS.json b/tests/checks/ave_eol_check/vmmDomP_POS.json similarity index 100% rename from tests/ave_eol_check/vmmDomP_POS.json rename to tests/checks/ave_eol_check/vmmDomP_POS.json diff --git a/tests/bgp_golf_route_target_type_check/fvCtx_pos.json b/tests/checks/bgp_golf_route_target_type_check/fvCtx_pos.json similarity index 100% rename from tests/bgp_golf_route_target_type_check/fvCtx_pos.json rename to tests/checks/bgp_golf_route_target_type_check/fvCtx_pos.json diff --git a/tests/bgp_golf_route_target_type_check/test_bgp_golf_route_target_type_check.py b/tests/checks/bgp_golf_route_target_type_check/test_bgp_golf_route_target_type_check.py similarity index 83% rename from tests/bgp_golf_route_target_type_check/test_bgp_golf_route_target_type_check.py rename to tests/checks/bgp_golf_route_target_type_check/test_bgp_golf_route_target_type_check.py index 2fc2e58d..56e71457 100644 --- a/tests/bgp_golf_route_target_type_check/test_bgp_golf_route_target_type_check.py +++ b/tests/checks/bgp_golf_route_target_type_check/test_bgp_golf_route_target_type_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "bgp_golf_route_target_type_check" # icurl queries fvCtxs = 'fvCtx.json?rsp-subtree=full&rsp-subtree-class=l3extGlobalCtxName,bgpRtTarget&rsp-subtree-include=required' @@ -61,11 +62,9 @@ ), ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.bgp_golf_route_target_type_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_neg.json b/tests/checks/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_neg.json similarity index 100% rename from tests/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_neg.json rename to tests/checks/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_neg.json diff --git a/tests/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos.json b/tests/checks/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos.json similarity index 100% rename from tests/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos.json rename to tests/checks/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos.json diff --git a/tests/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos1.json b/tests/checks/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos1.json similarity index 100% rename from tests/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos1.json rename to tests/checks/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos1.json diff --git a/tests/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos2.json b/tests/checks/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos2.json similarity index 100% rename from tests/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos2.json rename to tests/checks/bgp_peer_loopback_check/l3extRsNodeL3OutAtt_pos2.json diff --git a/tests/bgp_peer_loopback_check/test_bgp_peer_loopback_check.py b/tests/checks/bgp_peer_loopback_check/test_bgp_peer_loopback_check.py similarity index 87% rename from tests/bgp_peer_loopback_check/test_bgp_peer_loopback_check.py rename to tests/checks/bgp_peer_loopback_check/test_bgp_peer_loopback_check.py index a9e5d360..d08522ee 100644 --- a/tests/bgp_peer_loopback_check/test_bgp_peer_loopback_check.py +++ b/tests/checks/bgp_peer_loopback_check/test_bgp_peer_loopback_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "bgp_peer_loopback_check" # icurl queries l3extLNodePs = "l3extLNodeP.json?rsp-subtree=full&rsp-subtree-class=bgpPeerP,l3extRsNodeL3OutAtt,l3extLoopBackIfP" @@ -39,6 +40,6 @@ ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.bgp_peer_loopback_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/cimc_compatibilty_check/compatRsSuppHw_605_L2.json b/tests/checks/cimc_compatibilty_check/compatRsSuppHw_605_L2.json similarity index 100% rename from tests/cimc_compatibilty_check/compatRsSuppHw_605_L2.json rename to tests/checks/cimc_compatibilty_check/compatRsSuppHw_605_L2.json diff --git a/tests/cimc_compatibilty_check/compatRsSuppHw_605_M1.json b/tests/checks/cimc_compatibilty_check/compatRsSuppHw_605_M1.json similarity index 100% rename from tests/cimc_compatibilty_check/compatRsSuppHw_605_M1.json rename to tests/checks/cimc_compatibilty_check/compatRsSuppHw_605_M1.json diff --git a/tests/cimc_compatibilty_check/compatRsSuppHw_empty.json b/tests/checks/cimc_compatibilty_check/compatRsSuppHw_empty.json similarity index 100% rename from tests/cimc_compatibilty_check/compatRsSuppHw_empty.json rename to tests/checks/cimc_compatibilty_check/compatRsSuppHw_empty.json diff --git a/tests/cimc_compatibilty_check/eqptCh_newver.json b/tests/checks/cimc_compatibilty_check/eqptCh_newver.json similarity index 100% rename from tests/cimc_compatibilty_check/eqptCh_newver.json rename to tests/checks/cimc_compatibilty_check/eqptCh_newver.json diff --git a/tests/cimc_compatibilty_check/eqptCh_oldver.json b/tests/checks/cimc_compatibilty_check/eqptCh_oldver.json similarity index 100% rename from tests/cimc_compatibilty_check/eqptCh_oldver.json rename to tests/checks/cimc_compatibilty_check/eqptCh_oldver.json diff --git a/tests/cimc_compatibilty_check/eqptCh_reallyoldver.json b/tests/checks/cimc_compatibilty_check/eqptCh_reallyoldver.json similarity index 100% rename from tests/cimc_compatibilty_check/eqptCh_reallyoldver.json rename to tests/checks/cimc_compatibilty_check/eqptCh_reallyoldver.json diff --git a/tests/cimc_compatibilty_check/test_cimc_compatibilty_check.py b/tests/checks/cimc_compatibilty_check/test_cimc_compatibilty_check.py similarity index 60% rename from tests/cimc_compatibilty_check/test_cimc_compatibilty_check.py rename to tests/checks/cimc_compatibilty_check/test_cimc_compatibilty_check.py index 9ce8af6f..cb587fb3 100644 --- a/tests/cimc_compatibilty_check/test_cimc_compatibilty_check.py +++ b/tests/checks/cimc_compatibilty_check/test_cimc_compatibilty_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "cimc_compatibilty_check" # icurl queries eqptCh_api = 'eqptCh.json?query-target-filter=wcard(eqptCh.descr,"APIC")' @@ -16,40 +17,41 @@ compatRsSuppHwL2_api = 'uni/fabric/compcat-default/ctlrfw-apic-6.0(5)/rssuppHw-[uni/fabric/compcat-default/ctlrhw-apicl2].json' compatRsSuppHwM1_api = 'uni/fabric/compcat-default/ctlrfw-apic-6.0(5)/rssuppHw-[uni/fabric/compcat-default/ctlrhw-apicm1].json' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ ( {eqptCh_api: read_data(dir, "eqptCh_reallyoldver.json"), - compatRsSuppHwL2_api: read_data(dir, "compatRsSuppHw_605_L2.json"), - compatRsSuppHwM1_api: read_data(dir, "compatRsSuppHw_605_M1.json")}, + compatRsSuppHwL2_api: read_data(dir, "compatRsSuppHw_605_L2.json"), + compatRsSuppHwM1_api: read_data(dir, "compatRsSuppHw_605_M1.json")}, "6.0(5a)", script.FAIL_UF, ), ( {eqptCh_api: read_data(dir, "eqptCh_oldver.json"), - compatRsSuppHwL2_api: read_data(dir, "compatRsSuppHw_605_L2.json"), - compatRsSuppHwM1_api: read_data(dir, "compatRsSuppHw_605_M1.json")}, + compatRsSuppHwL2_api: read_data(dir, "compatRsSuppHw_605_L2.json"), + compatRsSuppHwM1_api: read_data(dir, "compatRsSuppHw_605_M1.json")}, "6.0(5a)", script.FAIL_UF, ), ( {eqptCh_api: read_data(dir, "eqptCh_newver.json"), - compatRsSuppHwL2_api: read_data(dir, "compatRsSuppHw_605_L2.json"), - compatRsSuppHwM1_api: read_data(dir, "compatRsSuppHw_605_M1.json")}, + compatRsSuppHwL2_api: read_data(dir, "compatRsSuppHw_605_L2.json"), + compatRsSuppHwM1_api: read_data(dir, "compatRsSuppHw_605_M1.json")}, "6.0(5a)", script.PASS, ), # Seen in QA testing where version + model does not have catalog entry ( {eqptCh_api: read_data(dir, "eqptCh_newver.json"), - compatRsSuppHwL2_api: read_data(dir, "compatRsSuppHw_605_L2.json"), - compatRsSuppHwM1_api: read_data(dir, "compatRsSuppHw_empty.json")}, + compatRsSuppHwL2_api: read_data(dir, "compatRsSuppHw_605_L2.json"), + compatRsSuppHwM1_api: read_data(dir, "compatRsSuppHw_empty.json")}, "6.0(5a)", script.MANUAL, ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.cimc_compatibilty_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/clock_signal_component_failure_check/eqptFC_NEG.json b/tests/checks/clock_signal_component_failure_check/eqptFC_NEG.json similarity index 100% rename from tests/clock_signal_component_failure_check/eqptFC_NEG.json rename to tests/checks/clock_signal_component_failure_check/eqptFC_NEG.json diff --git a/tests/clock_signal_component_failure_check/eqptFC_POS.json b/tests/checks/clock_signal_component_failure_check/eqptFC_POS.json similarity index 100% rename from tests/clock_signal_component_failure_check/eqptFC_POS.json rename to tests/checks/clock_signal_component_failure_check/eqptFC_POS.json diff --git a/tests/clock_signal_component_failure_check/eqptLC_NEG.json b/tests/checks/clock_signal_component_failure_check/eqptLC_NEG.json similarity index 100% rename from tests/clock_signal_component_failure_check/eqptLC_NEG.json rename to tests/checks/clock_signal_component_failure_check/eqptLC_NEG.json diff --git a/tests/clock_signal_component_failure_check/eqptLC_POS.json b/tests/checks/clock_signal_component_failure_check/eqptLC_POS.json similarity index 100% rename from tests/clock_signal_component_failure_check/eqptLC_POS.json rename to tests/checks/clock_signal_component_failure_check/eqptLC_POS.json diff --git a/tests/clock_signal_component_failure_check/test_clock_signal_component_failure_check.py b/tests/checks/clock_signal_component_failure_check/test_clock_signal_component_failure_check.py similarity index 88% rename from tests/clock_signal_component_failure_check/test_clock_signal_component_failure_check.py rename to tests/checks/clock_signal_component_failure_check/test_clock_signal_component_failure_check.py index bf1b6683..a1b017e5 100644 --- a/tests/clock_signal_component_failure_check/test_clock_signal_component_failure_check.py +++ b/tests/checks/clock_signal_component_failure_check/test_clock_signal_component_failure_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "clock_signal_component_failure_check" eqptFC_api = 'eqptFC.json' eqptFC_api += '?query-target-filter=or(eq(eqptFC.model,"N9K-C9504-FM-E"),eq(eqptFC.model,"N9K-C9508-FM-E"))' @@ -52,6 +53,6 @@ ) ], ) -def test_logic(mock_icurl,expected_result): - result = script.clock_signal_component_failure_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/cloudsec_encryption_depr_check/cloudsecPreSharedKey_err.json b/tests/checks/cloudsec_encryption_depr_check/cloudsecPreSharedKey_err.json similarity index 100% rename from tests/cloudsec_encryption_depr_check/cloudsecPreSharedKey_err.json rename to tests/checks/cloudsec_encryption_depr_check/cloudsecPreSharedKey_err.json diff --git a/tests/cloudsec_encryption_depr_check/cloudsecPreSharedKey_neg.json b/tests/checks/cloudsec_encryption_depr_check/cloudsecPreSharedKey_neg.json similarity index 100% rename from tests/cloudsec_encryption_depr_check/cloudsecPreSharedKey_neg.json rename to tests/checks/cloudsec_encryption_depr_check/cloudsecPreSharedKey_neg.json diff --git a/tests/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos.json b/tests/checks/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos.json similarity index 100% rename from tests/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos.json rename to tests/checks/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos.json diff --git a/tests/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos2.json b/tests/checks/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos2.json similarity index 100% rename from tests/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos2.json rename to tests/checks/cloudsec_encryption_depr_check/cloudsecPreSharedKey_pos2.json diff --git a/tests/cloudsec_encryption_depr_check/test_cloudsec_encryption_depr_check.py b/tests/checks/cloudsec_encryption_depr_check/test_cloudsec_encryption_depr_check.py similarity index 89% rename from tests/cloudsec_encryption_depr_check/test_cloudsec_encryption_depr_check.py rename to tests/checks/cloudsec_encryption_depr_check/test_cloudsec_encryption_depr_check.py index 13789148..16678e32 100644 --- a/tests/cloudsec_encryption_depr_check/test_cloudsec_encryption_depr_check.py +++ b/tests/checks/cloudsec_encryption_depr_check/test_cloudsec_encryption_depr_check.py @@ -10,6 +10,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "cloudsec_encryption_depr_check" # icurl queries cloudsecPreSharedKey = 'cloudsecPreSharedKey.json' @@ -56,6 +57,6 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.cloudsec_encryption_depr_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/configpush_shard_check/configpushShardCont_pos.json b/tests/checks/configpush_shard_check/configpushShardCont_pos.json similarity index 100% rename from tests/configpush_shard_check/configpushShardCont_pos.json rename to tests/checks/configpush_shard_check/configpushShardCont_pos.json diff --git a/tests/configpush_shard_check/test_configpush_shard_check.py b/tests/checks/configpush_shard_check/test_configpush_shard_check.py similarity index 86% rename from tests/configpush_shard_check/test_configpush_shard_check.py rename to tests/checks/configpush_shard_check/test_configpush_shard_check.py index 341f5665..3e482247 100644 --- a/tests/configpush_shard_check/test_configpush_shard_check.py +++ b/tests/checks/configpush_shard_check/test_configpush_shard_check.py @@ -9,10 +9,13 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "configpush_shard_check" + # icurl queries configpushShardCont_api = 'configpushShardCont.json' configpushShardCont_api += '?query-target-filter=and(eq(configpushShardCont.tailTx,"0"),ne(configpushShardCont.headTx,"0"))' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ @@ -50,7 +53,8 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - tversion = script.AciVersion(tversion) if tversion else None - result = script.configpush_shard_check(1, 1, tversion) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, + ) + assert result.result == expected_result diff --git a/tests/checks/conftest.py b/tests/checks/conftest.py new file mode 100644 index 00000000..01cd8870 --- /dev/null +++ b/tests/checks/conftest.py @@ -0,0 +1,137 @@ +import pytest +import logging +import importlib +from subprocess import CalledProcessError + +script = importlib.import_module("aci-preupgrade-validation-script") + +log = logging.getLogger() + + +@pytest.fixture +def run_check(request): + def _run_check(**kwargs): + test_function = getattr(request.module, "test_function") + cm = script.CheckManager(debug_function=test_function, monitor_interval=0.01) + cm.initialize_checks() + + err = "Unable to find test_function ({}) in CheckManager".format(test_function) + assert test_function in cm.check_ids, err + + cm.run_checks(common_data=kwargs) + + result = cm.get_check_result(test_function) + assert isinstance(result, script.Result) + + return result + + return _run_check + + +@pytest.fixture +def conn_failure(): + return False + + +@pytest.fixture +def conn_cmds(): + ''' + Set of test parameters for mocked `Connection.cmd()`. + + ex) + ``` + { + <apic_ip>: [{ + "cmd": "ls -aslh /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin", + "output": """\ + ls -aslh /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin + 6.1G -rwxr-xr-x 1 root root 6.1G Apr 3 16:36 /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin + f2-apic1# + """, + "exception": None + }] + } + ``` + + The real output from `Connection.cmd()` (i.e. `Connection.output`) contains many ANSI characters. In this fixture, those characters are not considered. + ''' + return {} + + +class MockConnection(script.Connection): + conn_failure = False + conn_cmds = None + + def connect(self): + """ + `Connection.connect()` is just instantiating `pexepect.spawn()` which does not + initiate the SSH connection yet. Not exception likely happens here. + """ + if self.conn_failure: + raise Exception("Simulated exception at connect()") + + def cmd(self, command, **kargs): + """ + `Connection.cmd()` initiates the SSH connection (if not done yet) and sends the command. + Each check typically has multiple `cmd()` with different commands. + To cover that, this mock func uses a dictionary `conn_cmds` as the test data. + """ + _conn_cmds = self.conn_cmds[self.hostname] + for conn_cmd in _conn_cmds: + if command == conn_cmd["cmd"]: + if conn_cmd["exception"]: + raise conn_cmd["exception"] + self.output = conn_cmd["output"] + break + else: + log.error("Command `%s` not found in test data `conn_cmds`", command) + raise Exception("FAILURE IN PYTEST") + + +@pytest.fixture +def mock_conn(monkeypatch, conn_failure, conn_cmds): + MockConnection.conn_failure = conn_failure + MockConnection.conn_cmds = conn_cmds + monkeypatch.setattr(script, "Connection", MockConnection) + + +@pytest.fixture +def cmd_outputs(): + """ + Mocked output for `run_cmd` function. + This is used to avoid executing real commands in tests. + """ + return { + "ls -aslh /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin": { + "splitlines": False, + "output": "6.1G -rwxr-xr-x 1 root root 6.1G Apr 3 16:36 /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin\napic1#", + } + } + + +@pytest.fixture +def mock_run_cmd(monkeypatch, cmd_outputs): + """ + Mock the `run_cmd` function to avoid executing real commands. + This is useful for tests that do not require actual command execution. + """ + def _mock_run_cmd(cmd, splitlines=False): + details = cmd_outputs.get(cmd) + if details is None: + log.error("Command `%s` not found in test data", cmd) + return "" + if details.get("CalledProcessError"): + raise CalledProcessError(127, cmd) + + splitlines = details.get("splitlines", False) + output = details.get("output") + if output is None: + log.error("Output for cmd `%s` not found in test data", cmd) + output = "" + + log.debug("Mocked run_cmd called with args: %s, kwargs: %s", cmd, splitlines) + if splitlines: + return output.splitlines() + else: + return output + monkeypatch.setattr(script, "run_cmd", _mock_run_cmd) diff --git a/tests/consumer_vzany_shared_services_check/epg_epg2_unmatched.json b/tests/checks/consumer_vzany_shared_services_check/epg_epg2_unmatched.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/epg_epg2_unmatched.json rename to tests/checks/consumer_vzany_shared_services_check/epg_epg2_unmatched.json diff --git a/tests/consumer_vzany_shared_services_check/esg_esg2.json b/tests/checks/consumer_vzany_shared_services_check/esg_esg2.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/esg_esg2.json rename to tests/checks/consumer_vzany_shared_services_check/esg_esg2.json diff --git a/tests/consumer_vzany_shared_services_check/fvCtx_consumer_same_vrf.json b/tests/checks/consumer_vzany_shared_services_check/fvCtx_consumer_same_vrf.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/fvCtx_consumer_same_vrf.json rename to tests/checks/consumer_vzany_shared_services_check/fvCtx_consumer_same_vrf.json diff --git a/tests/consumer_vzany_shared_services_check/fvCtx_consumer_shared.json b/tests/checks/consumer_vzany_shared_services_check/fvCtx_consumer_shared.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/fvCtx_consumer_shared.json rename to tests/checks/consumer_vzany_shared_services_check/fvCtx_consumer_shared.json diff --git a/tests/consumer_vzany_shared_services_check/fvCtx_no_consumers.json b/tests/checks/consumer_vzany_shared_services_check/fvCtx_no_consumers.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/fvCtx_no_consumers.json rename to tests/checks/consumer_vzany_shared_services_check/fvCtx_no_consumers.json diff --git a/tests/consumer_vzany_shared_services_check/global_contracts_epg_only.json b/tests/checks/consumer_vzany_shared_services_check/global_contracts_epg_only.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/global_contracts_epg_only.json rename to tests/checks/consumer_vzany_shared_services_check/global_contracts_epg_only.json diff --git a/tests/consumer_vzany_shared_services_check/global_contracts_esg_only.json b/tests/checks/consumer_vzany_shared_services_check/global_contracts_esg_only.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/global_contracts_esg_only.json rename to tests/checks/consumer_vzany_shared_services_check/global_contracts_esg_only.json diff --git a/tests/consumer_vzany_shared_services_check/global_contracts_same_vrf.json b/tests/checks/consumer_vzany_shared_services_check/global_contracts_same_vrf.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/global_contracts_same_vrf.json rename to tests/checks/consumer_vzany_shared_services_check/global_contracts_same_vrf.json diff --git a/tests/consumer_vzany_shared_services_check/global_contracts_shared.json b/tests/checks/consumer_vzany_shared_services_check/global_contracts_shared.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/global_contracts_shared.json rename to tests/checks/consumer_vzany_shared_services_check/global_contracts_shared.json diff --git a/tests/consumer_vzany_shared_services_check/instp_l3instp2.json b/tests/checks/consumer_vzany_shared_services_check/instp_l3instp2.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/instp_l3instp2.json rename to tests/checks/consumer_vzany_shared_services_check/instp_l3instp2.json diff --git a/tests/consumer_vzany_shared_services_check/test_consumer_vzany_shared_services_check.py b/tests/checks/consumer_vzany_shared_services_check/test_consumer_vzany_shared_services_check.py similarity index 97% rename from tests/consumer_vzany_shared_services_check/test_consumer_vzany_shared_services_check.py rename to tests/checks/consumer_vzany_shared_services_check/test_consumer_vzany_shared_services_check.py index 92019d2d..628dff95 100644 --- a/tests/consumer_vzany_shared_services_check/test_consumer_vzany_shared_services_check.py +++ b/tests/checks/consumer_vzany_shared_services_check/test_consumer_vzany_shared_services_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "consumer_vzany_shared_services_check" # icurl queries fvCtx_query = "fvCtx.json?rsp-subtree=full&rsp-subtree-class=vzRsAnyToCons" @@ -262,11 +263,9 @@ ), ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.consumer_vzany_shared_services_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/consumer_vzany_shared_services_check/vnsGraphInst_redirect.json b/tests/checks/consumer_vzany_shared_services_check/vnsGraphInst_redirect.json similarity index 100% rename from tests/consumer_vzany_shared_services_check/vnsGraphInst_redirect.json rename to tests/checks/consumer_vzany_shared_services_check/vnsGraphInst_redirect.json diff --git a/tests/contract_22_defect_check/test_contract_22_defect_check.py b/tests/checks/contract_22_defect_check/test_contract_22_defect_check.py similarity index 65% rename from tests/contract_22_defect_check/test_contract_22_defect_check.py rename to tests/checks/contract_22_defect_check/test_contract_22_defect_check.py index 1c9e174d..26b036c5 100644 --- a/tests/contract_22_defect_check/test_contract_22_defect_check.py +++ b/tests/checks/contract_22_defect_check/test_contract_22_defect_check.py @@ -8,6 +8,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "contract_22_defect_check" + @pytest.mark.parametrize( "cversion, tversion, expected_result", @@ -20,11 +22,9 @@ ("5.2(1a)", None, script.MANUAL), ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.contract_22_defect_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/docker0_subent_overlap_check/apContainerPol_10_0_0_1__16.json b/tests/checks/docker0_subent_overlap_check/apContainerPol_10_0_0_1__16.json similarity index 100% rename from tests/docker0_subent_overlap_check/apContainerPol_10_0_0_1__16.json rename to tests/checks/docker0_subent_overlap_check/apContainerPol_10_0_0_1__16.json diff --git a/tests/docker0_subent_overlap_check/apContainerPol_172_16_0_1__15.json b/tests/checks/docker0_subent_overlap_check/apContainerPol_172_16_0_1__15.json similarity index 100% rename from tests/docker0_subent_overlap_check/apContainerPol_172_16_0_1__15.json rename to tests/checks/docker0_subent_overlap_check/apContainerPol_172_16_0_1__15.json diff --git a/tests/docker0_subent_overlap_check/apContainerPol_172_17_0_10__16.json b/tests/checks/docker0_subent_overlap_check/apContainerPol_172_17_0_10__16.json similarity index 100% rename from tests/docker0_subent_overlap_check/apContainerPol_172_17_0_10__16.json rename to tests/checks/docker0_subent_overlap_check/apContainerPol_172_17_0_10__16.json diff --git a/tests/docker0_subent_overlap_check/apContainerPol_172_17_0_1__16.json b/tests/checks/docker0_subent_overlap_check/apContainerPol_172_17_0_1__16.json similarity index 100% rename from tests/docker0_subent_overlap_check/apContainerPol_172_17_0_1__16.json rename to tests/checks/docker0_subent_overlap_check/apContainerPol_172_17_0_1__16.json diff --git a/tests/docker0_subent_overlap_check/apContainerPol_172_17_0_1__17.json b/tests/checks/docker0_subent_overlap_check/apContainerPol_172_17_0_1__17.json similarity index 100% rename from tests/docker0_subent_overlap_check/apContainerPol_172_17_0_1__17.json rename to tests/checks/docker0_subent_overlap_check/apContainerPol_172_17_0_1__17.json diff --git a/tests/docker0_subent_overlap_check/apContainerPol_172_18_0_1__16.json b/tests/checks/docker0_subent_overlap_check/apContainerPol_172_18_0_1__16.json similarity index 100% rename from tests/docker0_subent_overlap_check/apContainerPol_172_18_0_1__16.json rename to tests/checks/docker0_subent_overlap_check/apContainerPol_172_18_0_1__16.json diff --git a/tests/docker0_subent_overlap_check/infraWiNode_10_0_0_0__16.json b/tests/checks/docker0_subent_overlap_check/infraWiNode_10_0_0_0__16.json similarity index 100% rename from tests/docker0_subent_overlap_check/infraWiNode_10_0_0_0__16.json rename to tests/checks/docker0_subent_overlap_check/infraWiNode_10_0_0_0__16.json diff --git a/tests/docker0_subent_overlap_check/infraWiNode_10_0_x_0__24_remote_apic.json b/tests/checks/docker0_subent_overlap_check/infraWiNode_10_0_x_0__24_remote_apic.json similarity index 100% rename from tests/docker0_subent_overlap_check/infraWiNode_10_0_x_0__24_remote_apic.json rename to tests/checks/docker0_subent_overlap_check/infraWiNode_10_0_x_0__24_remote_apic.json diff --git a/tests/docker0_subent_overlap_check/infraWiNode_172_17_0_0__16.json b/tests/checks/docker0_subent_overlap_check/infraWiNode_172_17_0_0__16.json similarity index 100% rename from tests/docker0_subent_overlap_check/infraWiNode_172_17_0_0__16.json rename to tests/checks/docker0_subent_overlap_check/infraWiNode_172_17_0_0__16.json diff --git a/tests/docker0_subent_overlap_check/infraWiNode_172_17_x_0__24_remote_apic.json b/tests/checks/docker0_subent_overlap_check/infraWiNode_172_17_x_0__24_remote_apic.json similarity index 100% rename from tests/docker0_subent_overlap_check/infraWiNode_172_17_x_0__24_remote_apic.json rename to tests/checks/docker0_subent_overlap_check/infraWiNode_172_17_x_0__24_remote_apic.json diff --git a/tests/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py b/tests/checks/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py similarity index 96% rename from tests/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py rename to tests/checks/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py index 89cd737a..a8d9da01 100644 --- a/tests/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py +++ b/tests/checks/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py @@ -3,11 +3,13 @@ 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__)) +test_function = "docker0_subnet_overlap_check" # icurl queries infraWiNode = "infraWiNode.json" @@ -149,6 +151,6 @@ ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.docker0_subnet_overlap_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/eecdh_cipher_check/commCipher_neg.json b/tests/checks/eecdh_cipher_check/commCipher_neg.json similarity index 100% rename from tests/eecdh_cipher_check/commCipher_neg.json rename to tests/checks/eecdh_cipher_check/commCipher_neg.json diff --git a/tests/eecdh_cipher_check/commCipher_neg2.json b/tests/checks/eecdh_cipher_check/commCipher_neg2.json similarity index 100% rename from tests/eecdh_cipher_check/commCipher_neg2.json rename to tests/checks/eecdh_cipher_check/commCipher_neg2.json diff --git a/tests/eecdh_cipher_check/commCipher_pos.json b/tests/checks/eecdh_cipher_check/commCipher_pos.json similarity index 100% rename from tests/eecdh_cipher_check/commCipher_pos.json rename to tests/checks/eecdh_cipher_check/commCipher_pos.json diff --git a/tests/eecdh_cipher_check/test_eecdh_cipher_check.py b/tests/checks/eecdh_cipher_check/test_eecdh_cipher_check.py similarity index 80% rename from tests/eecdh_cipher_check/test_eecdh_cipher_check.py rename to tests/checks/eecdh_cipher_check/test_eecdh_cipher_check.py index 297d3c90..f6a19865 100644 --- a/tests/eecdh_cipher_check/test_eecdh_cipher_check.py +++ b/tests/checks/eecdh_cipher_check/test_eecdh_cipher_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "eecdh_cipher_check" # icurl queries commCiphers = 'commCipher.json' @@ -39,10 +40,8 @@ ), ], ) -def test_logic(mock_icurl, cversion, expected_result): - result = script.eecdh_cipher_check( - 1, - 1, - script.AciVersion(cversion), +def test_logic(run_check, mock_icurl, cversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), ) - assert result == expected_result \ No newline at end of file + assert result.result == expected_result diff --git a/tests/encap_already_in_use_check/faultInst-encap-pos.json b/tests/checks/encap_already_in_use_check/faultInst-encap-pos.json similarity index 100% rename from tests/encap_already_in_use_check/faultInst-encap-pos.json rename to tests/checks/encap_already_in_use_check/faultInst-encap-pos.json diff --git a/tests/encap_already_in_use_check/faultInst-new-version.json b/tests/checks/encap_already_in_use_check/faultInst-new-version.json similarity index 100% rename from tests/encap_already_in_use_check/faultInst-new-version.json rename to tests/checks/encap_already_in_use_check/faultInst-new-version.json diff --git a/tests/encap_already_in_use_check/fvIfConn.json b/tests/checks/encap_already_in_use_check/fvIfConn.json similarity index 100% rename from tests/encap_already_in_use_check/fvIfConn.json rename to tests/checks/encap_already_in_use_check/fvIfConn.json diff --git a/tests/encap_already_in_use_check/test_encap_already_in_use_check.py b/tests/checks/encap_already_in_use_check/test_encap_already_in_use_check.py similarity index 86% rename from tests/encap_already_in_use_check/test_encap_already_in_use_check.py rename to tests/checks/encap_already_in_use_check/test_encap_already_in_use_check.py index 426ff4c5..e55645f4 100644 --- a/tests/encap_already_in_use_check/test_encap_already_in_use_check.py +++ b/tests/checks/encap_already_in_use_check/test_encap_already_in_use_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "encap_already_in_use_check" # icurl queries faultInsts = ( @@ -43,6 +44,6 @@ ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.encap_already_in_use_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/equipment_disk_limits_exceeded/faultInst_neg.json b/tests/checks/equipment_disk_limits_exceeded/faultInst_neg.json similarity index 100% rename from tests/equipment_disk_limits_exceeded/faultInst_neg.json rename to tests/checks/equipment_disk_limits_exceeded/faultInst_neg.json diff --git a/tests/equipment_disk_limits_exceeded/faultInst_pos.json b/tests/checks/equipment_disk_limits_exceeded/faultInst_pos.json similarity index 100% rename from tests/equipment_disk_limits_exceeded/faultInst_pos.json rename to tests/checks/equipment_disk_limits_exceeded/faultInst_pos.json diff --git a/tests/equipment_disk_limits_exceeded/test_equipment_disk_limits_exceeded.py b/tests/checks/equipment_disk_limits_exceeded/test_equipment_disk_limits_exceeded.py similarity index 79% rename from tests/equipment_disk_limits_exceeded/test_equipment_disk_limits_exceeded.py rename to tests/checks/equipment_disk_limits_exceeded/test_equipment_disk_limits_exceeded.py index 9399c700..72fd0aa9 100644 --- a/tests/equipment_disk_limits_exceeded/test_equipment_disk_limits_exceeded.py +++ b/tests/checks/equipment_disk_limits_exceeded/test_equipment_disk_limits_exceeded.py @@ -9,9 +9,12 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "equipment_disk_limits_exceeded" + f182x_api = 'faultInst.json' f182x_api += '?query-target-filter=or(eq(faultInst.code,"F1820"),eq(faultInst.code,"F1821"),eq(faultInst.code,"F1822"))' + @pytest.mark.parametrize( "icurl_outputs, expected_result", [ @@ -25,6 +28,6 @@ ) ], ) -def test_logic(mock_icurl, expected_result): - result = script.equipment_disk_limits_exceeded(1, 1) - assert result == expected_result \ No newline at end of file +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/eventmgr_db_defect_check/test_eventmgr_db_defect_check.py b/tests/checks/eventmgr_db_defect_check/test_eventmgr_db_defect_check.py similarity index 77% rename from tests/eventmgr_db_defect_check/test_eventmgr_db_defect_check.py rename to tests/checks/eventmgr_db_defect_check/test_eventmgr_db_defect_check.py index 4524d602..c4f98ddf 100644 --- a/tests/eventmgr_db_defect_check/test_eventmgr_db_defect_check.py +++ b/tests/checks/eventmgr_db_defect_check/test_eventmgr_db_defect_check.py @@ -8,6 +8,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "eventmgr_db_defect_check" + @pytest.mark.parametrize( "cversion, expected_result", @@ -27,8 +29,8 @@ ("5.0(1l)", script.PASS), ], ) -def test_logic(mock_icurl, cversion, expected_result): - result = script.eventmgr_db_defect_check( - 1, 1, script.AciVersion(cversion) +def test_logic(run_check, mock_icurl, cversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/fabricPathEP_target_check/fabricRsOosPath_neg.json b/tests/checks/fabricPathEP_target_check/fabricRsOosPath_neg.json similarity index 100% rename from tests/fabricPathEP_target_check/fabricRsOosPath_neg.json rename to tests/checks/fabricPathEP_target_check/fabricRsOosPath_neg.json diff --git a/tests/fabricPathEP_target_check/fabricRsOosPath_pos1.json b/tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos1.json similarity index 100% rename from tests/fabricPathEP_target_check/fabricRsOosPath_pos1.json rename to tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos1.json diff --git a/tests/fabricPathEP_target_check/fabricRsOosPath_pos2.json b/tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos2.json similarity index 100% rename from tests/fabricPathEP_target_check/fabricRsOosPath_pos2.json rename to tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos2.json diff --git a/tests/fabricPathEP_target_check/fabricRsOosPath_pos3.json b/tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos3.json similarity index 100% rename from tests/fabricPathEP_target_check/fabricRsOosPath_pos3.json rename to tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos3.json diff --git a/tests/fabricPathEP_target_check/fabricRsOosPath_pos4.json b/tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos4.json similarity index 100% rename from tests/fabricPathEP_target_check/fabricRsOosPath_pos4.json rename to tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos4.json diff --git a/tests/fabricPathEP_target_check/fabricRsOosPath_pos5.json b/tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos5.json similarity index 100% rename from tests/fabricPathEP_target_check/fabricRsOosPath_pos5.json rename to tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos5.json diff --git a/tests/fabricPathEP_target_check/fabricRsOosPath_pos6.json b/tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos6.json similarity index 100% rename from tests/fabricPathEP_target_check/fabricRsOosPath_pos6.json rename to tests/checks/fabricPathEP_target_check/fabricRsOosPath_pos6.json diff --git a/tests/fabricPathEP_target_check/infraRsHPathAtt_neg.json b/tests/checks/fabricPathEP_target_check/infraRsHPathAtt_neg.json similarity index 100% rename from tests/fabricPathEP_target_check/infraRsHPathAtt_neg.json rename to tests/checks/fabricPathEP_target_check/infraRsHPathAtt_neg.json diff --git a/tests/fabricPathEP_target_check/infraRsHPathAtt_pos1.json b/tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos1.json similarity index 100% rename from tests/fabricPathEP_target_check/infraRsHPathAtt_pos1.json rename to tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos1.json diff --git a/tests/fabricPathEP_target_check/infraRsHPathAtt_pos2.json b/tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos2.json similarity index 100% rename from tests/fabricPathEP_target_check/infraRsHPathAtt_pos2.json rename to tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos2.json diff --git a/tests/fabricPathEP_target_check/infraRsHPathAtt_pos3.json b/tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos3.json similarity index 100% rename from tests/fabricPathEP_target_check/infraRsHPathAtt_pos3.json rename to tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos3.json diff --git a/tests/fabricPathEP_target_check/infraRsHPathAtt_pos4.json b/tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos4.json similarity index 100% rename from tests/fabricPathEP_target_check/infraRsHPathAtt_pos4.json rename to tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos4.json diff --git a/tests/fabricPathEP_target_check/infraRsHPathAtt_pos5.json b/tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos5.json similarity index 100% rename from tests/fabricPathEP_target_check/infraRsHPathAtt_pos5.json rename to tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos5.json diff --git a/tests/fabricPathEP_target_check/infraRsHPathAtt_pos6.json b/tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos6.json similarity index 100% rename from tests/fabricPathEP_target_check/infraRsHPathAtt_pos6.json rename to tests/checks/fabricPathEP_target_check/infraRsHPathAtt_pos6.json diff --git a/tests/fabricPathEP_target_check/test_fabricPathEP_target_check.py b/tests/checks/fabricPathEP_target_check/test_fabricPathEP_target_check.py similarity index 89% rename from tests/fabricPathEP_target_check/test_fabricPathEP_target_check.py rename to tests/checks/fabricPathEP_target_check/test_fabricPathEP_target_check.py index b136d7c6..98fdd4ec 100644 --- a/tests/fabricPathEP_target_check/test_fabricPathEP_target_check.py +++ b/tests/checks/fabricPathEP_target_check/test_fabricPathEP_target_check.py @@ -9,10 +9,12 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "fabricPathEp_target_check" # icurl queries -hpath_api = 'infraRsHPathAtt.json' -oosPorts_api = 'fabricRsOosPath.json' +hpath_api = 'infraRsHPathAtt.json' +oosPorts_api = 'fabricRsOosPath.json' + @pytest.mark.parametrize( "icurl_outputs, expected_result", @@ -62,6 +64,6 @@ ], ) -def test_logic(mock_icurl, expected_result): - result = script.fabricPathEp_target_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/fabric_dpp_check/lbpPol_NEG.json b/tests/checks/fabric_dpp_check/lbpPol_NEG.json similarity index 100% rename from tests/fabric_dpp_check/lbpPol_NEG.json rename to tests/checks/fabric_dpp_check/lbpPol_NEG.json diff --git a/tests/fabric_dpp_check/lbpPol_POS.json b/tests/checks/fabric_dpp_check/lbpPol_POS.json similarity index 100% rename from tests/fabric_dpp_check/lbpPol_POS.json rename to tests/checks/fabric_dpp_check/lbpPol_POS.json diff --git a/tests/fabric_dpp_check/test_fabric_dpp_check.py b/tests/checks/fabric_dpp_check/test_fabric_dpp_check.py similarity index 85% rename from tests/fabric_dpp_check/test_fabric_dpp_check.py rename to tests/checks/fabric_dpp_check/test_fabric_dpp_check.py index 9eacfb7e..6e2fddd2 100644 --- a/tests/fabric_dpp_check/test_fabric_dpp_check.py +++ b/tests/checks/fabric_dpp_check/test_fabric_dpp_check.py @@ -9,11 +9,13 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "fabric_dpp_check" # icurl queries -lbpPol = 'lbpPol.json' +lbpPol = 'lbpPol.json' lbpPol += '?query-target-filter=eq(lbpPol.pri,"on")' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ @@ -62,8 +64,8 @@ ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.fabric_dpp_check( - 1, 1, script.AciVersion(tversion) +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion), ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/fabric_link_redundancy_check/fabricNode.json b/tests/checks/fabric_link_redundancy_check/fabricNode.json similarity index 100% rename from tests/fabric_link_redundancy_check/fabricNode.json rename to tests/checks/fabric_link_redundancy_check/fabricNode.json diff --git a/tests/fabric_link_redundancy_check/lldpAdjEp_neg.json b/tests/checks/fabric_link_redundancy_check/lldpAdjEp_neg.json similarity index 100% rename from tests/fabric_link_redundancy_check/lldpAdjEp_neg.json rename to tests/checks/fabric_link_redundancy_check/lldpAdjEp_neg.json diff --git a/tests/fabric_link_redundancy_check/lldpAdjEp_pos_spine_only.json b/tests/checks/fabric_link_redundancy_check/lldpAdjEp_pos_spine_only.json similarity index 100% rename from tests/fabric_link_redundancy_check/lldpAdjEp_pos_spine_only.json rename to tests/checks/fabric_link_redundancy_check/lldpAdjEp_pos_spine_only.json diff --git a/tests/fabric_link_redundancy_check/lldpAdjEp_pos_spine_t1.json b/tests/checks/fabric_link_redundancy_check/lldpAdjEp_pos_spine_t1.json similarity index 100% rename from tests/fabric_link_redundancy_check/lldpAdjEp_pos_spine_t1.json rename to tests/checks/fabric_link_redundancy_check/lldpAdjEp_pos_spine_t1.json diff --git a/tests/fabric_link_redundancy_check/lldpAdjEp_pos_t1_only.json b/tests/checks/fabric_link_redundancy_check/lldpAdjEp_pos_t1_only.json similarity index 100% rename from tests/fabric_link_redundancy_check/lldpAdjEp_pos_t1_only.json rename to tests/checks/fabric_link_redundancy_check/lldpAdjEp_pos_t1_only.json diff --git a/tests/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py b/tests/checks/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py similarity index 91% rename from tests/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py rename to tests/checks/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py index 3570cce5..2716d620 100644 --- a/tests/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py +++ b/tests/checks/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "fabric_link_redundancy_check" fabric_nodes_api = 'fabricNode.json' fabric_nodes_api += '?query-target-filter=and(or(eq(fabricNode.role,"leaf"),eq(fabricNode.role,"spine")),eq(fabricNode.fabricSt,"active"))' @@ -56,6 +57,6 @@ ), ], ) -def test_logic(mock_icurl , expected_result): - result = script.fabric_link_redundancy_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl , expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/fabric_port_down_check/faultInst_pos.json b/tests/checks/fabric_port_down_check/faultInst_pos.json similarity index 100% rename from tests/fabric_port_down_check/faultInst_pos.json rename to tests/checks/fabric_port_down_check/faultInst_pos.json diff --git a/tests/fabric_port_down_check/test_fabric_port_down_check.py b/tests/checks/fabric_port_down_check/test_fabric_port_down_check.py similarity index 78% rename from tests/fabric_port_down_check/test_fabric_port_down_check.py rename to tests/checks/fabric_port_down_check/test_fabric_port_down_check.py index 034e343a..4b132334 100644 --- a/tests/fabric_port_down_check/test_fabric_port_down_check.py +++ b/tests/checks/fabric_port_down_check/test_fabric_port_down_check.py @@ -9,9 +9,10 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "fabric_port_down_check" # icurl queries -faultInsts = 'faultInst.json' +faultInsts = 'faultInst.json' faultInsts += '?&query-target-filter=and(eq(faultInst.code,"F1394")' faultInsts += ',eq(faultInst.rule,"ethpm-if-port-down-fabric"))' @@ -33,6 +34,6 @@ ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.fabric_port_down_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/fabricdomain_name_check/test_fabricdomain_name_check.py b/tests/checks/fabricdomain_name_check/test_fabricdomain_name_check.py similarity index 87% rename from tests/fabricdomain_name_check/test_fabricdomain_name_check.py rename to tests/checks/fabricdomain_name_check/test_fabricdomain_name_check.py index 61ae397b..0ec2d6b0 100644 --- a/tests/fabricdomain_name_check/test_fabricdomain_name_check.py +++ b/tests/checks/fabricdomain_name_check/test_fabricdomain_name_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "fabricdomain_name_check" # icurl queries topSystem = 'topSystem.json?query-target-filter=eq(topSystem.role,"controller")' @@ -72,8 +73,9 @@ ), ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.fabricdomain_name_check( - 1, 1, script.AciVersion(cversion), script.AciVersion(tversion) +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion), ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/fabricdomain_name_check/topSystem_1POS.json b/tests/checks/fabricdomain_name_check/topSystem_1POS.json similarity index 100% rename from tests/fabricdomain_name_check/topSystem_1POS.json rename to tests/checks/fabricdomain_name_check/topSystem_1POS.json diff --git a/tests/fabricdomain_name_check/topSystem_2POS.json b/tests/checks/fabricdomain_name_check/topSystem_2POS.json similarity index 100% rename from tests/fabricdomain_name_check/topSystem_2POS.json rename to tests/checks/fabricdomain_name_check/topSystem_2POS.json diff --git a/tests/fabricdomain_name_check/topSystem_NEG.json b/tests/checks/fabricdomain_name_check/topSystem_NEG.json similarity index 100% rename from tests/fabricdomain_name_check/topSystem_NEG.json rename to tests/checks/fabricdomain_name_check/topSystem_NEG.json diff --git a/tests/fc_ex_model_check/fabricNode_NEG.json b/tests/checks/fc_ex_model_check/fabricNode_NEG.json similarity index 100% rename from tests/fc_ex_model_check/fabricNode_NEG.json rename to tests/checks/fc_ex_model_check/fabricNode_NEG.json diff --git a/tests/fc_ex_model_check/fabricNode_POS.json b/tests/checks/fc_ex_model_check/fabricNode_POS.json similarity index 100% rename from tests/fc_ex_model_check/fabricNode_POS.json rename to tests/checks/fc_ex_model_check/fabricNode_POS.json diff --git a/tests/fc_ex_model_check/fcEntity_NEG.json b/tests/checks/fc_ex_model_check/fcEntity_NEG.json similarity index 100% rename from tests/fc_ex_model_check/fcEntity_NEG.json rename to tests/checks/fc_ex_model_check/fcEntity_NEG.json diff --git a/tests/fc_ex_model_check/fcEntity_POS.json b/tests/checks/fc_ex_model_check/fcEntity_POS.json similarity index 100% rename from tests/fc_ex_model_check/fcEntity_POS.json rename to tests/checks/fc_ex_model_check/fcEntity_POS.json diff --git a/tests/fc_ex_model_check/test_fc_ex_model_check.py b/tests/checks/fc_ex_model_check/test_fc_ex_model_check.py similarity index 89% rename from tests/fc_ex_model_check/test_fc_ex_model_check.py rename to tests/checks/fc_ex_model_check/test_fc_ex_model_check.py index 1cd0833a..82b59e52 100644 --- a/tests/fc_ex_model_check/test_fc_ex_model_check.py +++ b/tests/checks/fc_ex_model_check/test_fc_ex_model_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "fc_ex_model_check" # icurl queries @@ -16,6 +17,7 @@ fabricNode_api = 'fabricNode.json' fabricNode_api += '?query-target-filter=wcard(fabricNode.model,".*EX")' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ @@ -60,6 +62,6 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.fc_ex_model_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/helpers/__init__.py b/tests/checks/helpers/__init__.py similarity index 100% rename from tests/helpers/__init__.py rename to tests/checks/helpers/__init__.py diff --git a/tests/helpers/utils.py b/tests/checks/helpers/utils.py similarity index 68% rename from tests/helpers/utils.py rename to tests/checks/helpers/utils.py index 98fa913a..10236f13 100644 --- a/tests/helpers/utils.py +++ b/tests/checks/helpers/utils.py @@ -3,7 +3,7 @@ def read_data(dir, json_file): - data_path = os.path.join("tests", dir, json_file) + data_path = os.path.join("tests", "checks", dir, json_file) with open(data_path, "r") as file: data = json.load(file) return data diff --git a/tests/https_throttle_rate_check/commHttps_neg1.json b/tests/checks/https_throttle_rate_check/commHttps_neg1.json similarity index 100% rename from tests/https_throttle_rate_check/commHttps_neg1.json rename to tests/checks/https_throttle_rate_check/commHttps_neg1.json diff --git a/tests/https_throttle_rate_check/commHttps_neg2.json b/tests/checks/https_throttle_rate_check/commHttps_neg2.json similarity index 100% rename from tests/https_throttle_rate_check/commHttps_neg2.json rename to tests/checks/https_throttle_rate_check/commHttps_neg2.json diff --git a/tests/https_throttle_rate_check/commHttps_pos.json b/tests/checks/https_throttle_rate_check/commHttps_pos.json similarity index 100% rename from tests/https_throttle_rate_check/commHttps_pos.json rename to tests/checks/https_throttle_rate_check/commHttps_pos.json diff --git a/tests/https_throttle_rate_check/test_https_throttle_rate_check.py b/tests/checks/https_throttle_rate_check/test_https_throttle_rate_check.py similarity index 88% rename from tests/https_throttle_rate_check/test_https_throttle_rate_check.py rename to tests/checks/https_throttle_rate_check/test_https_throttle_rate_check.py index f79edf26..7f2f9a68 100644 --- a/tests/https_throttle_rate_check/test_https_throttle_rate_check.py +++ b/tests/checks/https_throttle_rate_check/test_https_throttle_rate_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "https_throttle_rate_check" # icurl queries commHttps = "commHttps.json" @@ -75,8 +76,9 @@ ), ], ) -def test_logic(mock_icurl, cver, tver, expected_result): - cversion = script.AciVersion(cver) - tversion = script.AciVersion(tver) if tver else None - result = script.https_throttle_rate_check(1, 1, cversion, tversion) - assert result == expected_result +def test_logic(run_check, mock_icurl, cver, tver, expected_result): + result = run_check( + cversion=script.AciVersion(cver), + tversion=script.AciVersion(tver) if tver else None, + ) + assert result.result == expected_result diff --git a/tests/internal_vlanpool_check/fvnsVlanInstP_neg.json b/tests/checks/internal_vlanpool_check/fvnsVlanInstP_neg.json similarity index 100% rename from tests/internal_vlanpool_check/fvnsVlanInstP_neg.json rename to tests/checks/internal_vlanpool_check/fvnsVlanInstP_neg.json diff --git a/tests/internal_vlanpool_check/fvnsVlanInstP_pos.json b/tests/checks/internal_vlanpool_check/fvnsVlanInstP_pos.json similarity index 100% rename from tests/internal_vlanpool_check/fvnsVlanInstP_pos.json rename to tests/checks/internal_vlanpool_check/fvnsVlanInstP_pos.json diff --git a/tests/internal_vlanpool_check/test_internal_vlanpool_check.py b/tests/checks/internal_vlanpool_check/test_internal_vlanpool_check.py similarity index 93% rename from tests/internal_vlanpool_check/test_internal_vlanpool_check.py rename to tests/checks/internal_vlanpool_check/test_internal_vlanpool_check.py index 6d16279b..704b3350 100644 --- a/tests/internal_vlanpool_check/test_internal_vlanpool_check.py +++ b/tests/checks/internal_vlanpool_check/test_internal_vlanpool_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "internal_vlanpool_check" # icurl queries fvnsVlanInstPs = "fvnsVlanInstP.json?rsp-subtree=children&rsp-subtree-class=fvnsRtVlanNs,fvnsEncapBlk&rsp-subtree-include=required" @@ -100,6 +101,6 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.internal_vlanpool_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/internal_vlanpool_check/vmmDomP_neg.json b/tests/checks/internal_vlanpool_check/vmmDomP_neg.json similarity index 100% rename from tests/internal_vlanpool_check/vmmDomP_neg.json rename to tests/checks/internal_vlanpool_check/vmmDomP_neg.json diff --git a/tests/internal_vlanpool_check/vmmDomP_pos.json b/tests/checks/internal_vlanpool_check/vmmDomP_pos.json similarity index 100% rename from tests/internal_vlanpool_check/vmmDomP_pos.json rename to tests/checks/internal_vlanpool_check/vmmDomP_pos.json diff --git a/tests/isis_database_byte_check/isisDTEp_NEG.json b/tests/checks/isis_database_byte_check/isisDTEp_NEG.json similarity index 100% rename from tests/isis_database_byte_check/isisDTEp_NEG.json rename to tests/checks/isis_database_byte_check/isisDTEp_NEG.json diff --git a/tests/isis_database_byte_check/isisDTEp_POS.json b/tests/checks/isis_database_byte_check/isisDTEp_POS.json similarity index 100% rename from tests/isis_database_byte_check/isisDTEp_POS.json rename to tests/checks/isis_database_byte_check/isisDTEp_POS.json diff --git a/tests/isis_database_byte_check/test_isis_database_byte_check.py b/tests/checks/isis_database_byte_check/test_isis_database_byte_check.py similarity index 88% rename from tests/isis_database_byte_check/test_isis_database_byte_check.py rename to tests/checks/isis_database_byte_check/test_isis_database_byte_check.py index 39003bab..538e132e 100644 --- a/tests/isis_database_byte_check/test_isis_database_byte_check.py +++ b/tests/checks/isis_database_byte_check/test_isis_database_byte_check.py @@ -9,10 +9,13 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "isis_database_byte_check" + # icurl queries isisDTEp_api = 'isisDTEp.json' isisDTEp_api += '?query-target-filter=eq(isisDTEp.role,"spine")' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ @@ -75,7 +78,8 @@ ) ] ) -def test_logic(mock_icurl, tversion, expected_result): - tversion = script.AciVersion(tversion) if tversion else None - result = script.isis_database_byte_check(1, 1, tversion) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, + ) + assert result.result == expected_result diff --git a/tests/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos1.json b/tests/checks/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos1.json similarity index 100% rename from tests/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos1.json rename to tests/checks/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos1.json diff --git a/tests/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos2.json b/tests/checks/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos2.json similarity index 100% rename from tests/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos2.json rename to tests/checks/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos2.json diff --git a/tests/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos3.json b/tests/checks/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos3.json similarity index 100% rename from tests/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos3.json rename to tests/checks/isis_redis_metric_mpod_msite_check/fvFabricExtConnP_pos3.json diff --git a/tests/isis_redis_metric_mpod_msite_check/isisDomP-default_missing.json b/tests/checks/isis_redis_metric_mpod_msite_check/isisDomP-default_missing.json similarity index 100% rename from tests/isis_redis_metric_mpod_msite_check/isisDomP-default_missing.json rename to tests/checks/isis_redis_metric_mpod_msite_check/isisDomP-default_missing.json diff --git a/tests/isis_redis_metric_mpod_msite_check/isisDomP-default_neg.json b/tests/checks/isis_redis_metric_mpod_msite_check/isisDomP-default_neg.json similarity index 100% rename from tests/isis_redis_metric_mpod_msite_check/isisDomP-default_neg.json rename to tests/checks/isis_redis_metric_mpod_msite_check/isisDomP-default_neg.json diff --git a/tests/isis_redis_metric_mpod_msite_check/isisDomP-default_pos.json b/tests/checks/isis_redis_metric_mpod_msite_check/isisDomP-default_pos.json similarity index 100% rename from tests/isis_redis_metric_mpod_msite_check/isisDomP-default_pos.json rename to tests/checks/isis_redis_metric_mpod_msite_check/isisDomP-default_pos.json diff --git a/tests/isis_redis_metric_mpod_msite_check/test_isis_redis_metric_mpod_msite_check.py b/tests/checks/isis_redis_metric_mpod_msite_check/test_isis_redis_metric_mpod_msite_check.py similarity index 90% rename from tests/isis_redis_metric_mpod_msite_check/test_isis_redis_metric_mpod_msite_check.py rename to tests/checks/isis_redis_metric_mpod_msite_check/test_isis_redis_metric_mpod_msite_check.py index 153c248f..a159de8a 100644 --- a/tests/isis_redis_metric_mpod_msite_check/test_isis_redis_metric_mpod_msite_check.py +++ b/tests/checks/isis_redis_metric_mpod_msite_check/test_isis_redis_metric_mpod_msite_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "isis_redis_metric_mpod_msite_check" # icurl queries isisDomPs = "uni/fabric/isisDomP-default.json" @@ -55,6 +56,6 @@ ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.isis_redis_metric_mpod_msite_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/l3out_mtu_check/l2pol-default.json b/tests/checks/l3out_mtu_check/l2pol-default.json similarity index 100% rename from tests/l3out_mtu_check/l2pol-default.json rename to tests/checks/l3out_mtu_check/l2pol-default.json diff --git a/tests/l3out_mtu_check/l3extRsPathL3OutAtt.json b/tests/checks/l3out_mtu_check/l3extRsPathL3OutAtt.json similarity index 100% rename from tests/l3out_mtu_check/l3extRsPathL3OutAtt.json rename to tests/checks/l3out_mtu_check/l3extRsPathL3OutAtt.json diff --git a/tests/l3out_mtu_check/l3extVirtualLIfP.json b/tests/checks/l3out_mtu_check/l3extVirtualLIfP.json similarity index 100% rename from tests/l3out_mtu_check/l3extVirtualLIfP.json rename to tests/checks/l3out_mtu_check/l3extVirtualLIfP.json diff --git a/tests/l3out_mtu_check/l3extVirtualLIfP_unresolved.json b/tests/checks/l3out_mtu_check/l3extVirtualLIfP_unresolved.json similarity index 100% rename from tests/l3out_mtu_check/l3extVirtualLIfP_unresolved.json rename to tests/checks/l3out_mtu_check/l3extVirtualLIfP_unresolved.json diff --git a/tests/l3out_mtu_check/test_l3out_mtu_check.py b/tests/checks/l3out_mtu_check/test_l3out_mtu_check.py similarity index 92% rename from tests/l3out_mtu_check/test_l3out_mtu_check.py rename to tests/checks/l3out_mtu_check/test_l3out_mtu_check.py index ee74eb3d..50986acd 100644 --- a/tests/l3out_mtu_check/test_l3out_mtu_check.py +++ b/tests/checks/l3out_mtu_check/test_l3out_mtu_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "l3out_mtu_check" # icurl queries regular_api = "l3extRsPathL3OutAtt.json" @@ -47,6 +48,6 @@ ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.l3out_mtu_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/l3out_overlapping_loopback_check/diff_l3out_loopback.json b/tests/checks/l3out_overlapping_loopback_check/diff_l3out_loopback.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/diff_l3out_loopback.json rename to tests/checks/l3out_overlapping_loopback_check/diff_l3out_loopback.json diff --git a/tests/l3out_overlapping_loopback_check/diff_l3out_loopback_and_rtrId.json b/tests/checks/l3out_overlapping_loopback_check/diff_l3out_loopback_and_rtrId.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/diff_l3out_loopback_and_rtrId.json rename to tests/checks/l3out_overlapping_loopback_check/diff_l3out_loopback_and_rtrId.json diff --git a/tests/l3out_overlapping_loopback_check/diff_l3out_rtrId.json b/tests/checks/l3out_overlapping_loopback_check/diff_l3out_rtrId.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/diff_l3out_rtrId.json rename to tests/checks/l3out_overlapping_loopback_check/diff_l3out_rtrId.json diff --git a/tests/l3out_overlapping_loopback_check/no_overlap.json b/tests/checks/l3out_overlapping_loopback_check/no_overlap.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/no_overlap.json rename to tests/checks/l3out_overlapping_loopback_check/no_overlap.json diff --git a/tests/l3out_overlapping_loopback_check/overlap_on_diff_nodes.json b/tests/checks/l3out_overlapping_loopback_check/overlap_on_diff_nodes.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/overlap_on_diff_nodes.json rename to tests/checks/l3out_overlapping_loopback_check/overlap_on_diff_nodes.json diff --git a/tests/l3out_overlapping_loopback_check/same_l3out_loopback.json b/tests/checks/l3out_overlapping_loopback_check/same_l3out_loopback.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/same_l3out_loopback.json rename to tests/checks/l3out_overlapping_loopback_check/same_l3out_loopback.json diff --git a/tests/l3out_overlapping_loopback_check/same_l3out_loopback_and_rtrId.json b/tests/checks/l3out_overlapping_loopback_check/same_l3out_loopback_and_rtrId.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/same_l3out_loopback_and_rtrId.json rename to tests/checks/l3out_overlapping_loopback_check/same_l3out_loopback_and_rtrId.json diff --git a/tests/l3out_overlapping_loopback_check/same_l3out_loopback_with_subnet_mask.json b/tests/checks/l3out_overlapping_loopback_check/same_l3out_loopback_with_subnet_mask.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/same_l3out_loopback_with_subnet_mask.json rename to tests/checks/l3out_overlapping_loopback_check/same_l3out_loopback_with_subnet_mask.json diff --git a/tests/l3out_overlapping_loopback_check/same_l3out_rtrId.json b/tests/checks/l3out_overlapping_loopback_check/same_l3out_rtrId.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/same_l3out_rtrId.json rename to tests/checks/l3out_overlapping_loopback_check/same_l3out_rtrId.json diff --git a/tests/l3out_overlapping_loopback_check/same_l3out_rtrId_non_vpc.json b/tests/checks/l3out_overlapping_loopback_check/same_l3out_rtrId_non_vpc.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/same_l3out_rtrId_non_vpc.json rename to tests/checks/l3out_overlapping_loopback_check/same_l3out_rtrId_non_vpc.json diff --git a/tests/l3out_overlapping_loopback_check/same_l3out_two_loopbacks.json b/tests/checks/l3out_overlapping_loopback_check/same_l3out_two_loopbacks.json similarity index 100% rename from tests/l3out_overlapping_loopback_check/same_l3out_two_loopbacks.json rename to tests/checks/l3out_overlapping_loopback_check/same_l3out_two_loopbacks.json diff --git a/tests/l3out_overlapping_loopback_check/test_l3out_overlapping_loopback_check.py b/tests/checks/l3out_overlapping_loopback_check/test_l3out_overlapping_loopback_check.py similarity index 92% rename from tests/l3out_overlapping_loopback_check/test_l3out_overlapping_loopback_check.py rename to tests/checks/l3out_overlapping_loopback_check/test_l3out_overlapping_loopback_check.py index b91cfff8..0235de8f 100644 --- a/tests/l3out_overlapping_loopback_check/test_l3out_overlapping_loopback_check.py +++ b/tests/checks/l3out_overlapping_loopback_check/test_l3out_overlapping_loopback_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "l3out_overlapping_loopback_check" # icurl queries api = 'l3extOut.json' @@ -43,6 +44,6 @@ ({api: read_data(dir, "diff_l3out_loopback_and_rtrId.json")}, script.FAIL_O), ], ) -def test_logic(mock_icurl, expected_result): - result = script.l3out_overlapping_loopback_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/l3out_route_map_missing_target_check/rtctrlProfile_missing_target.json b/tests/checks/l3out_route_map_missing_target_check/rtctrlProfile_missing_target.json similarity index 100% rename from tests/l3out_route_map_missing_target_check/rtctrlProfile_missing_target.json rename to tests/checks/l3out_route_map_missing_target_check/rtctrlProfile_missing_target.json diff --git a/tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_l3out_multiple_missing_target.json b/tests/checks/l3out_route_map_missing_target_check/rtctrlProfile_multiple_l3out_multiple_missing_target.json similarity index 100% rename from tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_l3out_multiple_missing_target.json rename to tests/checks/l3out_route_map_missing_target_check/rtctrlProfile_multiple_l3out_multiple_missing_target.json diff --git a/tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_missing_target.json b/tests/checks/l3out_route_map_missing_target_check/rtctrlProfile_multiple_missing_target.json similarity index 100% rename from tests/l3out_route_map_missing_target_check/rtctrlProfile_multiple_missing_target.json rename to tests/checks/l3out_route_map_missing_target_check/rtctrlProfile_multiple_missing_target.json diff --git a/tests/l3out_route_map_missing_target_check/rtctrlProfile_no_missing_target.json b/tests/checks/l3out_route_map_missing_target_check/rtctrlProfile_no_missing_target.json similarity index 100% rename from tests/l3out_route_map_missing_target_check/rtctrlProfile_no_missing_target.json rename to tests/checks/l3out_route_map_missing_target_check/rtctrlProfile_no_missing_target.json diff --git a/tests/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py b/tests/checks/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py similarity index 86% rename from tests/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py rename to tests/checks/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py index 045d9aae..8abce063 100644 --- a/tests/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py +++ b/tests/checks/l3out_route_map_missing_target_check/test_l3out_route_map_missing_target_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "l3out_route_map_missing_target_check" # icurl queries profiles = 'rtctrlProfile.json' @@ -63,6 +64,9 @@ ), ], ) -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 +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion), + ) + assert result.result == expected_result diff --git a/tests/lldp_custom_int_description_defect_check/fvRsDomAtt_neg.json b/tests/checks/lldp_custom_int_description_defect_check/fvRsDomAtt_neg.json similarity index 100% rename from tests/lldp_custom_int_description_defect_check/fvRsDomAtt_neg.json rename to tests/checks/lldp_custom_int_description_defect_check/fvRsDomAtt_neg.json diff --git a/tests/lldp_custom_int_description_defect_check/fvRsDomAtt_pos.json b/tests/checks/lldp_custom_int_description_defect_check/fvRsDomAtt_pos.json similarity index 100% rename from tests/lldp_custom_int_description_defect_check/fvRsDomAtt_pos.json rename to tests/checks/lldp_custom_int_description_defect_check/fvRsDomAtt_pos.json diff --git a/tests/lldp_custom_int_description_defect_check/infraPortBlk_neg.json b/tests/checks/lldp_custom_int_description_defect_check/infraPortBlk_neg.json similarity index 100% rename from tests/lldp_custom_int_description_defect_check/infraPortBlk_neg.json rename to tests/checks/lldp_custom_int_description_defect_check/infraPortBlk_neg.json diff --git a/tests/lldp_custom_int_description_defect_check/infraPortBlk_pos.json b/tests/checks/lldp_custom_int_description_defect_check/infraPortBlk_pos.json similarity index 100% rename from tests/lldp_custom_int_description_defect_check/infraPortBlk_pos.json rename to tests/checks/lldp_custom_int_description_defect_check/infraPortBlk_pos.json diff --git a/tests/lldp_custom_int_description_defect_check/test_lldp_custom_int_description_defect_check.py b/tests/checks/lldp_custom_int_description_defect_check/test_lldp_custom_int_description_defect_check.py similarity index 91% rename from tests/lldp_custom_int_description_defect_check/test_lldp_custom_int_description_defect_check.py rename to tests/checks/lldp_custom_int_description_defect_check/test_lldp_custom_int_description_defect_check.py index 20f86ac4..72a4fb87 100644 --- a/tests/lldp_custom_int_description_defect_check/test_lldp_custom_int_description_defect_check.py +++ b/tests/checks/lldp_custom_int_description_defect_check/test_lldp_custom_int_description_defect_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "lldp_custom_int_description_defect_check" # icurl queries infraPortBlks = 'infraPortBlk.json?query-target-filter=ne(infraPortBlk.descr,"")&rsp-subtree-include=count' @@ -84,6 +85,6 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.lldp_custom_int_description_defect_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/llfc_susceptibility_check/ethpmFcot.json b/tests/checks/llfc_susceptibility_check/ethpmFcot.json similarity index 100% rename from tests/llfc_susceptibility_check/ethpmFcot.json rename to tests/checks/llfc_susceptibility_check/ethpmFcot.json diff --git a/tests/llfc_susceptibility_check/test_llfc_susceptibility_check.py b/tests/checks/llfc_susceptibility_check/test_llfc_susceptibility_check.py similarity index 86% rename from tests/llfc_susceptibility_check/test_llfc_susceptibility_check.py rename to tests/checks/llfc_susceptibility_check/test_llfc_susceptibility_check.py index c86b7454..eb90593d 100644 --- a/tests/llfc_susceptibility_check/test_llfc_susceptibility_check.py +++ b/tests/checks/llfc_susceptibility_check/test_llfc_susceptibility_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "llfc_susceptibility_check" # icurl queries ethpmFcots = 'ethpmFcot.json?query-target-filter=and(eq(ethpmFcot.type,"sfp"),eq(ethpmFcot.state,"inserted"))' @@ -75,12 +76,10 @@ ), ], ) -def test_logic(mock_icurl, cversion, tversion, vpc_node_ids, expected_result): - result = script.llfc_susceptibility_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, cversion, tversion, vpc_node_ids, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None, vpc_node_ids=vpc_node_ids, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/mini_aci_6_0_2/test_mini_aci_6_0_2_check.py b/tests/checks/mini_aci_6_0_2_check/test_mini_aci_6_0_2_check.py similarity index 84% rename from tests/mini_aci_6_0_2/test_mini_aci_6_0_2_check.py rename to tests/checks/mini_aci_6_0_2_check/test_mini_aci_6_0_2_check.py index c93578e2..a9514461 100644 --- a/tests/mini_aci_6_0_2/test_mini_aci_6_0_2_check.py +++ b/tests/checks/mini_aci_6_0_2_check/test_mini_aci_6_0_2_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "mini_aci_6_0_2_check" # icurl queries topSystems = 'topSystem.json?query-target-filter=wcard(topSystem.role,"controller")' @@ -61,11 +62,9 @@ ), ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.mini_aci_6_0_2_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/mini_aci_6_0_2/topSystem_controller_neg.json b/tests/checks/mini_aci_6_0_2_check/topSystem_controller_neg.json similarity index 100% rename from tests/mini_aci_6_0_2/topSystem_controller_neg.json rename to tests/checks/mini_aci_6_0_2_check/topSystem_controller_neg.json diff --git a/tests/mini_aci_6_0_2/topSystem_controller_pos.json b/tests/checks/mini_aci_6_0_2_check/topSystem_controller_pos.json similarity index 100% rename from tests/mini_aci_6_0_2/topSystem_controller_pos.json rename to tests/checks/mini_aci_6_0_2_check/topSystem_controller_pos.json diff --git a/tests/n9408_model_check/eqptCh_NEG.json b/tests/checks/n9408_model_check/eqptCh_NEG.json similarity index 100% rename from tests/n9408_model_check/eqptCh_NEG.json rename to tests/checks/n9408_model_check/eqptCh_NEG.json diff --git a/tests/n9408_model_check/eqptCh_POS.json b/tests/checks/n9408_model_check/eqptCh_POS.json similarity index 100% rename from tests/n9408_model_check/eqptCh_POS.json rename to tests/checks/n9408_model_check/eqptCh_POS.json diff --git a/tests/n9408_model_check/test_n9408_model_check.py b/tests/checks/n9408_model_check/test_n9408_model_check.py similarity index 82% rename from tests/n9408_model_check/test_n9408_model_check.py rename to tests/checks/n9408_model_check/test_n9408_model_check.py index 86d01b40..3076de1e 100644 --- a/tests/n9408_model_check/test_n9408_model_check.py +++ b/tests/checks/n9408_model_check/test_n9408_model_check.py @@ -9,12 +9,14 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "n9408_model_check" # icurl queries eqptCh_api = 'eqptCh.json' eqptCh_api += '?query-target-filter=eq(eqptCh.model,"N9K-C9400-SW-GX2A")' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ @@ -38,6 +40,6 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.n9408_model_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json similarity index 100% rename from tests/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json rename to tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json diff --git a/tests/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json similarity index 100% rename from tests/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json rename to tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json diff --git a/tests/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json similarity index 100% rename from tests/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json rename to tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json diff --git a/tests/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py similarity index 87% rename from tests/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py rename to tests/checks/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py index 2c35bff6..65b6dd41 100644 --- a/tests/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py +++ b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "n9k_c93108tc_fx3p_interface_down_check" # icurl queries api = 'fabricNode.json?query-target-filter=or(eq(fabricNode.model,"N9K-C93108TC-FX3P"),eq(fabricNode.model,"N9K-C93108TC-FX3H"))' @@ -41,10 +42,8 @@ ({api: read_data(dir, "fabricNode_FX3P3H.json")}, "6.0(2h)", script.FAIL_O), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.n9k_c93108tc_fx3p_interface_down_check( - 1, - 1, - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/observer_db_size_check/test_observer_db_size_check.py b/tests/checks/observer_db_size_check/test_observer_db_size_check.py similarity index 93% rename from tests/observer_db_size_check/test_observer_db_size_check.py rename to tests/checks/observer_db_size_check/test_observer_db_size_check.py index 9a856635..21bfa916 100644 --- a/tests/observer_db_size_check/test_observer_db_size_check.py +++ b/tests/checks/observer_db_size_check/test_observer_db_size_check.py @@ -9,6 +9,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "observer_db_size_check" + topSystem_api = 'topSystem.json' topSystem_api += '?query-target-filter=eq(topSystem.role,"controller")' @@ -40,6 +42,7 @@ apic1# """ + @pytest.mark.parametrize( "icurl_outputs, conn_failure, conn_cmds, expected_result", [ @@ -123,6 +126,6 @@ ), ], ) -def test_logic(mock_icurl, mock_conn, expected_result): - result = script.observer_db_size_check(1, 1, "fake_username", "fake_password") - assert result == expected_result +def test_logic(run_check, mock_icurl, mock_conn, expected_result): + result = run_check(username="fake_username", password="fake_password") + assert result.result == expected_result diff --git a/tests/observer_db_size_check/topSystem.json b/tests/checks/observer_db_size_check/topSystem.json similarity index 100% rename from tests/observer_db_size_check/topSystem.json rename to tests/checks/observer_db_size_check/topSystem.json diff --git a/tests/observer_db_size_check/topSystem_empty.json b/tests/checks/observer_db_size_check/topSystem_empty.json similarity index 100% rename from tests/observer_db_size_check/topSystem_empty.json rename to tests/checks/observer_db_size_check/topSystem_empty.json diff --git a/tests/oob_mgmt_security_check/mgmtInstP.json b/tests/checks/oob_mgmt_security_check/mgmtInstP.json similarity index 100% rename from tests/oob_mgmt_security_check/mgmtInstP.json rename to tests/checks/oob_mgmt_security_check/mgmtInstP.json diff --git a/tests/oob_mgmt_security_check/mgmtInstP_no_contracts.json b/tests/checks/oob_mgmt_security_check/mgmtInstP_no_contracts.json similarity index 100% rename from tests/oob_mgmt_security_check/mgmtInstP_no_contracts.json rename to tests/checks/oob_mgmt_security_check/mgmtInstP_no_contracts.json diff --git a/tests/oob_mgmt_security_check/mgmtInstP_no_subnets.json b/tests/checks/oob_mgmt_security_check/mgmtInstP_no_subnets.json similarity index 100% rename from tests/oob_mgmt_security_check/mgmtInstP_no_subnets.json rename to tests/checks/oob_mgmt_security_check/mgmtInstP_no_subnets.json diff --git a/tests/oob_mgmt_security_check/mgmtOoB.json b/tests/checks/oob_mgmt_security_check/mgmtOoB.json similarity index 100% rename from tests/oob_mgmt_security_check/mgmtOoB.json rename to tests/checks/oob_mgmt_security_check/mgmtOoB.json diff --git a/tests/oob_mgmt_security_check/mgmtOoB_no_contracts.json b/tests/checks/oob_mgmt_security_check/mgmtOoB_no_contracts.json similarity index 100% rename from tests/oob_mgmt_security_check/mgmtOoB_no_contracts.json rename to tests/checks/oob_mgmt_security_check/mgmtOoB_no_contracts.json diff --git a/tests/oob_mgmt_security_check/test_oob_mgmt_security_check.py b/tests/checks/oob_mgmt_security_check/test_oob_mgmt_security_check.py similarity index 92% rename from tests/oob_mgmt_security_check/test_oob_mgmt_security_check.py rename to tests/checks/oob_mgmt_security_check/test_oob_mgmt_security_check.py index 95b33b12..86e093a6 100644 --- a/tests/oob_mgmt_security_check/test_oob_mgmt_security_check.py +++ b/tests/checks/oob_mgmt_security_check/test_oob_mgmt_security_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "oob_mgmt_security_check" # icurl queries mgmtOoB = "mgmtOoB.json?rsp-subtree=children" @@ -129,8 +130,9 @@ ), ], ) -def test_logic(mock_icurl, cver, tver, expected_result): - cversion = script.AciVersion(cver) - tversion = script.AciVersion(tver) if tver else None - result = script.oob_mgmt_security_check(1, 1, cversion, tversion) - assert result == expected_result +def test_logic(run_check, mock_icurl, cver, tver, expected_result): + result = run_check( + cversion=script.AciVersion(cver), + tversion=script.AciVersion(tver) if tver else None, + ) + assert result.result == expected_result diff --git a/tests/out_of_service_ports_check/ethpmPhysIf-neg.json b/tests/checks/out_of_service_ports_check/ethpmPhysIf-neg.json similarity index 100% rename from tests/out_of_service_ports_check/ethpmPhysIf-neg.json rename to tests/checks/out_of_service_ports_check/ethpmPhysIf-neg.json diff --git a/tests/out_of_service_ports_check/ethpmPhysIf-pos.json b/tests/checks/out_of_service_ports_check/ethpmPhysIf-pos.json similarity index 100% rename from tests/out_of_service_ports_check/ethpmPhysIf-pos.json rename to tests/checks/out_of_service_ports_check/ethpmPhysIf-pos.json diff --git a/tests/out_of_service_ports_check/test_out_of_service_ports_check.py b/tests/checks/out_of_service_ports_check/test_out_of_service_ports_check.py similarity index 75% rename from tests/out_of_service_ports_check/test_out_of_service_ports_check.py rename to tests/checks/out_of_service_ports_check/test_out_of_service_ports_check.py index 069677eb..4296eeec 100644 --- a/tests/out_of_service_ports_check/test_out_of_service_ports_check.py +++ b/tests/checks/out_of_service_ports_check/test_out_of_service_ports_check.py @@ -9,6 +9,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "out_of_service_ports_check" + # operst: '1' = 'up' # usage: '32' = 'blacklist', '2' = 'epg'. '34'= 'blacklist,epg' ethpmPhysIf_api = 'ethpmPhysIf.json' @@ -19,17 +21,17 @@ "icurl_outputs, expected_result", [ ( - ## Two 'up' ports flagged with 'blacklist,epg' + # Two 'up' ports flagged with 'blacklist,epg' {ethpmPhysIf_api: read_data(dir, "ethpmPhysIf-pos.json")}, script.FAIL_O, ), ( - ## 0 ports returned + # 0 ports returned {ethpmPhysIf_api: read_data(dir, "ethpmPhysIf-neg.json")}, script.PASS, ) ], ) -def test_logic(mock_icurl, expected_result): - result = script.out_of_service_ports_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/overlapping_vlan_pools_check/access_policy.json b/tests/checks/overlapping_vlan_pools_check/access_policy.json similarity index 100% rename from tests/overlapping_vlan_pools_check/access_policy.json rename to tests/checks/overlapping_vlan_pools_check/access_policy.json diff --git a/tests/overlapping_vlan_pools_check/fvAEPg.json b/tests/checks/overlapping_vlan_pools_check/fvAEPg.json similarity index 100% rename from tests/overlapping_vlan_pools_check/fvAEPg.json rename to tests/checks/overlapping_vlan_pools_check/fvAEPg.json diff --git a/tests/overlapping_vlan_pools_check/fvIfConn.json b/tests/checks/overlapping_vlan_pools_check/fvIfConn.json similarity index 100% rename from tests/overlapping_vlan_pools_check/fvIfConn.json rename to tests/checks/overlapping_vlan_pools_check/fvIfConn.json diff --git a/tests/overlapping_vlan_pools_check/infraSetPol_no.json b/tests/checks/overlapping_vlan_pools_check/infraSetPol_no.json similarity index 100% rename from tests/overlapping_vlan_pools_check/infraSetPol_no.json rename to tests/checks/overlapping_vlan_pools_check/infraSetPol_no.json diff --git a/tests/overlapping_vlan_pools_check/infraSetPol_yes.json b/tests/checks/overlapping_vlan_pools_check/infraSetPol_yes.json similarity index 100% rename from tests/overlapping_vlan_pools_check/infraSetPol_yes.json rename to tests/checks/overlapping_vlan_pools_check/infraSetPol_yes.json diff --git a/tests/overlapping_vlan_pools_check/templates/access_policy.j2 b/tests/checks/overlapping_vlan_pools_check/templates/access_policy.j2 similarity index 100% rename from tests/overlapping_vlan_pools_check/templates/access_policy.j2 rename to tests/checks/overlapping_vlan_pools_check/templates/access_policy.j2 diff --git a/tests/overlapping_vlan_pools_check/templates/fvAEPg.j2 b/tests/checks/overlapping_vlan_pools_check/templates/fvAEPg.j2 similarity index 100% rename from tests/overlapping_vlan_pools_check/templates/fvAEPg.j2 rename to tests/checks/overlapping_vlan_pools_check/templates/fvAEPg.j2 diff --git a/tests/overlapping_vlan_pools_check/templates/fvIfConn.j2 b/tests/checks/overlapping_vlan_pools_check/templates/fvIfConn.j2 similarity index 100% rename from tests/overlapping_vlan_pools_check/templates/fvIfConn.j2 rename to tests/checks/overlapping_vlan_pools_check/templates/fvIfConn.j2 diff --git a/tests/overlapping_vlan_pools_check/templates/macros.j2 b/tests/checks/overlapping_vlan_pools_check/templates/macros.j2 similarity index 100% rename from tests/overlapping_vlan_pools_check/templates/macros.j2 rename to tests/checks/overlapping_vlan_pools_check/templates/macros.j2 diff --git a/tests/overlapping_vlan_pools_check/test_overlapping_vlan_pools_check.py b/tests/checks/overlapping_vlan_pools_check/test_overlapping_vlan_pools_check.py similarity index 99% rename from tests/overlapping_vlan_pools_check/test_overlapping_vlan_pools_check.py rename to tests/checks/overlapping_vlan_pools_check/test_overlapping_vlan_pools_check.py index cf6f6ee6..24a31f02 100644 --- a/tests/overlapping_vlan_pools_check/test_overlapping_vlan_pools_check.py +++ b/tests/checks/overlapping_vlan_pools_check/test_overlapping_vlan_pools_check.py @@ -11,6 +11,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "overlapping_vlan_pools_check" + j2_env = Environment(loader=FileSystemLoader("/".join([dir, "templates"]))) tmpl = { "infra": j2_env.get_template("access_policy.j2"), @@ -2384,15 +2386,7 @@ def input(param): + [input(param) for param in params], ids=["validation_yes", "validation_no"] + [param["id"] for param in params], ) -def test_logic(capsys, mock_icurl, expected_result, expected_num_bad_ports): - result = script.overlapping_vlan_pools_check(1, 1) - assert result == expected_result - - captured = capsys.readouterr() - log.debug(captured.out) - lines = [ - x - for x in captured.out.split("\n") - if x.endswith("Outage") or x.endswith("Flood Scope") - ] - assert len(lines) == expected_num_bad_ports +def test_logic(run_check, mock_icurl, expected_result, expected_num_bad_ports): + result = run_check() + assert result.result == expected_result + assert len(result.data) == expected_num_bad_ports diff --git a/tests/pbr_high_scale_check/test_pbr_high_scale_check.py b/tests/checks/pbr_high_scale_check/test_pbr_high_scale_check.py similarity index 90% rename from tests/pbr_high_scale_check/test_pbr_high_scale_check.py rename to tests/checks/pbr_high_scale_check/test_pbr_high_scale_check.py index 5a498f41..9516bea6 100644 --- a/tests/pbr_high_scale_check/test_pbr_high_scale_check.py +++ b/tests/checks/pbr_high_scale_check/test_pbr_high_scale_check.py @@ -9,12 +9,14 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "pbr_high_scale_check" # icurl queries vnsAdjacencyDefCont_api = 'vnsAdjacencyDefCont.json' vnsSvcRedirEcmpBucketCons_api = 'vnsSvcRedirEcmpBucketCons.json' count_filter = '?rsp-subtree-include=count' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ @@ -65,10 +67,8 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.pbr_high_scale_check( - 1, - 1, - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/pbr_high_scale_check/vnsAdjacencyDefCont_HIGH.json b/tests/checks/pbr_high_scale_check/vnsAdjacencyDefCont_HIGH.json similarity index 100% rename from tests/pbr_high_scale_check/vnsAdjacencyDefCont_HIGH.json rename to tests/checks/pbr_high_scale_check/vnsAdjacencyDefCont_HIGH.json diff --git a/tests/pbr_high_scale_check/vnsAdjacencyDefCont_LOW.json b/tests/checks/pbr_high_scale_check/vnsAdjacencyDefCont_LOW.json similarity index 100% rename from tests/pbr_high_scale_check/vnsAdjacencyDefCont_LOW.json rename to tests/checks/pbr_high_scale_check/vnsAdjacencyDefCont_LOW.json diff --git a/tests/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_HIGH.json b/tests/checks/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_HIGH.json similarity index 100% rename from tests/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_HIGH.json rename to tests/checks/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_HIGH.json diff --git a/tests/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_LOW.json b/tests/checks/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_LOW.json similarity index 100% rename from tests/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_LOW.json rename to tests/checks/pbr_high_scale_check/vnsSvcRedirEcmpBucketCons_LOW.json diff --git a/tests/post_upgrade_cb_check/moCount_0.json b/tests/checks/post_upgrade_cb_check/moCount_0.json similarity index 100% rename from tests/post_upgrade_cb_check/moCount_0.json rename to tests/checks/post_upgrade_cb_check/moCount_0.json diff --git a/tests/post_upgrade_cb_check/moCount_10.json b/tests/checks/post_upgrade_cb_check/moCount_10.json similarity index 100% rename from tests/post_upgrade_cb_check/moCount_10.json rename to tests/checks/post_upgrade_cb_check/moCount_10.json diff --git a/tests/post_upgrade_cb_check/moCount_8.json b/tests/checks/post_upgrade_cb_check/moCount_8.json similarity index 100% rename from tests/post_upgrade_cb_check/moCount_8.json rename to tests/checks/post_upgrade_cb_check/moCount_8.json diff --git a/tests/post_upgrade_cb_check/test_post_upgrade_cb_check.py b/tests/checks/post_upgrade_cb_check/test_post_upgrade_cb_check.py similarity index 92% rename from tests/post_upgrade_cb_check/test_post_upgrade_cb_check.py rename to tests/checks/post_upgrade_cb_check/test_post_upgrade_cb_check.py index c964ad97..fb14efc2 100644 --- a/tests/post_upgrade_cb_check/test_post_upgrade_cb_check.py +++ b/tests/checks/post_upgrade_cb_check/test_post_upgrade_cb_check.py @@ -9,6 +9,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "post_upgrade_cb_check" + # icurl queries mo1_new = "infraRsToImplicitSetPol.json?rsp-subtree-include=count" @@ -29,7 +31,6 @@ mo6_new = 'compatSwitchHw.json?rsp-subtree-include=count&query-target-filter=eq(compatSwitchHw.suppBit,"32")' - # icurl output sets mo_count_pass = { mo1_new: read_data(dir, "moCount_10.json"), @@ -96,11 +97,9 @@ (mo_count_fail, "6.0(3e)", "6.0(3e)", script.FAIL_O), ] ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.post_upgrade_cb_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_new.json b/tests/checks/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_new.json similarity index 100% rename from tests/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_new.json rename to tests/checks/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_new.json diff --git a/tests/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_old.json b/tests/checks/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_old.json similarity index 100% rename from tests/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_old.json rename to tests/checks/prefix_already_in_use_check/faultInst_F0467_prefix-entry-already-in-use_old.json diff --git a/tests/prefix_already_in_use_check/fvCtx.json b/tests/checks/prefix_already_in_use_check/fvCtx.json similarity index 100% rename from tests/prefix_already_in_use_check/fvCtx.json rename to tests/checks/prefix_already_in_use_check/fvCtx.json diff --git a/tests/prefix_already_in_use_check/l3extRsEctx.json b/tests/checks/prefix_already_in_use_check/l3extRsEctx.json similarity index 100% rename from tests/prefix_already_in_use_check/l3extRsEctx.json rename to tests/checks/prefix_already_in_use_check/l3extRsEctx.json diff --git a/tests/prefix_already_in_use_check/l3extSubnet_no_overlap.json b/tests/checks/prefix_already_in_use_check/l3extSubnet_no_overlap.json similarity index 100% rename from tests/prefix_already_in_use_check/l3extSubnet_no_overlap.json rename to tests/checks/prefix_already_in_use_check/l3extSubnet_no_overlap.json diff --git a/tests/prefix_already_in_use_check/l3extSubnet_overlap.json b/tests/checks/prefix_already_in_use_check/l3extSubnet_overlap.json similarity index 100% rename from tests/prefix_already_in_use_check/l3extSubnet_overlap.json rename to tests/checks/prefix_already_in_use_check/l3extSubnet_overlap.json diff --git a/tests/prefix_already_in_use_check/test_prefix_already_in_use_check.py b/tests/checks/prefix_already_in_use_check/test_prefix_already_in_use_check.py similarity index 91% rename from tests/prefix_already_in_use_check/test_prefix_already_in_use_check.py rename to tests/checks/prefix_already_in_use_check/test_prefix_already_in_use_check.py index 8871eb70..10fe53bd 100644 --- a/tests/prefix_already_in_use_check/test_prefix_already_in_use_check.py +++ b/tests/checks/prefix_already_in_use_check/test_prefix_already_in_use_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "prefix_already_in_use_check" # icurl queries faultInst = 'faultInst.json?query-target-filter=and(wcard(faultInst.changeSet,"prefix-entry-already-in-use"),wcard(faultInst.dn,"uni/epp/rtd"))' @@ -55,6 +56,6 @@ ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.prefix_already_in_use_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/rtmap_comm_match_defect_check/rtctrlCtxP_NEG.json b/tests/checks/rtmap_comm_match_defect_check/rtctrlCtxP_NEG.json similarity index 100% rename from tests/rtmap_comm_match_defect_check/rtctrlCtxP_NEG.json rename to tests/checks/rtmap_comm_match_defect_check/rtctrlCtxP_NEG.json diff --git a/tests/rtmap_comm_match_defect_check/rtctrlCtxP_POS.json b/tests/checks/rtmap_comm_match_defect_check/rtctrlCtxP_POS.json similarity index 100% rename from tests/rtmap_comm_match_defect_check/rtctrlCtxP_POS.json rename to tests/checks/rtmap_comm_match_defect_check/rtctrlCtxP_POS.json diff --git a/tests/rtmap_comm_match_defect_check/rtctrlSubjP_NEG.json b/tests/checks/rtmap_comm_match_defect_check/rtctrlSubjP_NEG.json similarity index 100% rename from tests/rtmap_comm_match_defect_check/rtctrlSubjP_NEG.json rename to tests/checks/rtmap_comm_match_defect_check/rtctrlSubjP_NEG.json diff --git a/tests/rtmap_comm_match_defect_check/rtctrlSubjP_POS.json b/tests/checks/rtmap_comm_match_defect_check/rtctrlSubjP_POS.json similarity index 100% rename from tests/rtmap_comm_match_defect_check/rtctrlSubjP_POS.json rename to tests/checks/rtmap_comm_match_defect_check/rtctrlSubjP_POS.json diff --git a/tests/rtmap_comm_match_defect_check/test_rtmap_comm_match_defect_check.py b/tests/checks/rtmap_comm_match_defect_check/test_rtmap_comm_match_defect_check.py similarity index 88% rename from tests/rtmap_comm_match_defect_check/test_rtmap_comm_match_defect_check.py rename to tests/checks/rtmap_comm_match_defect_check/test_rtmap_comm_match_defect_check.py index c090d508..2dc9000b 100644 --- a/tests/rtmap_comm_match_defect_check/test_rtmap_comm_match_defect_check.py +++ b/tests/checks/rtmap_comm_match_defect_check/test_rtmap_comm_match_defect_check.py @@ -9,11 +9,13 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "rtmap_comm_match_defect_check" # icurl queries rtctrlSubjPs = "rtctrlSubjP.json?rsp-subtree=full&rsp-subtree-class=rtctrlMatchCommFactor,rtctrlMatchRtDest&rsp-subtree-include=required" rtctrlCtxPs = "rtctrlCtxP.json?rsp-subtree=full&rsp-subtree-class=rtctrlRsCtxPToSubjP,rtctrlRsScopeToAttrP&rsp-subtree-include=required" + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ @@ -67,10 +69,8 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.rtmap_comm_match_defect_check( - 1, - 1, - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/service_bd_forceful_routing_check/fvRtEPpInfoToBD.json b/tests/checks/service_bd_forceful_routing_check/fvRtEPpInfoToBD.json similarity index 100% rename from tests/service_bd_forceful_routing_check/fvRtEPpInfoToBD.json rename to tests/checks/service_bd_forceful_routing_check/fvRtEPpInfoToBD.json diff --git a/tests/service_bd_forceful_routing_check/test_service_bd_forceful_routing_check.py b/tests/checks/service_bd_forceful_routing_check/test_service_bd_forceful_routing_check.py similarity index 81% rename from tests/service_bd_forceful_routing_check/test_service_bd_forceful_routing_check.py rename to tests/checks/service_bd_forceful_routing_check/test_service_bd_forceful_routing_check.py index 86038b39..5867c884 100644 --- a/tests/service_bd_forceful_routing_check/test_service_bd_forceful_routing_check.py +++ b/tests/checks/service_bd_forceful_routing_check/test_service_bd_forceful_routing_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "service_bd_forceful_routing_check" # icurl queries fvRtEPpInfoToBD = "fvRtEPpInfoToBD.json" @@ -54,8 +55,9 @@ ), ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - cver = script.AciVersion(cversion) - tver = script.AciVersion(tversion) if tversion else None - result = script.service_bd_forceful_routing_check(1, 1, cver, tver) - assert result == expected_result +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None, + ) + assert result.result == expected_result diff --git a/tests/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json b/tests/checks/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json similarity index 100% rename from tests/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json rename to tests/checks/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json diff --git a/tests/stale_decomissioned_spine_check/fabricRsDecommissionNode_POS.json b/tests/checks/stale_decomissioned_spine_check/fabricRsDecommissionNode_POS.json similarity index 100% rename from tests/stale_decomissioned_spine_check/fabricRsDecommissionNode_POS.json rename to tests/checks/stale_decomissioned_spine_check/fabricRsDecommissionNode_POS.json diff --git a/tests/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py b/tests/checks/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py similarity index 80% rename from tests/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py rename to tests/checks/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py index fade8136..fff0653d 100644 --- a/tests/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py +++ b/tests/checks/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py @@ -9,12 +9,14 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "stale_decomissioned_spine_check" # icurl queries -decomissioned_api ='fabricRsDecommissionNode.json' +decomissioned_api = 'fabricRsDecommissionNode.json' active_spine_api = 'topSystem.json' -active_spine_api += '?query-target-filter=eq(topSystem.role,"spine")' +active_spine_api += '?query-target-filter=eq(topSystem.role,"spine")' + @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", @@ -57,10 +59,8 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.stale_decomissioned_spine_check( - 1, - 1, - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/stale_decomissioned_spine_check/topSystem.json b/tests/checks/stale_decomissioned_spine_check/topSystem.json similarity index 100% rename from tests/stale_decomissioned_spine_check/topSystem.json rename to tests/checks/stale_decomissioned_spine_check/topSystem.json diff --git a/tests/standby_sup_sync_check/eqptSupC_NEG.json b/tests/checks/standby_sup_sync_check/eqptSupC_NEG.json similarity index 100% rename from tests/standby_sup_sync_check/eqptSupC_NEG.json rename to tests/checks/standby_sup_sync_check/eqptSupC_NEG.json diff --git a/tests/standby_sup_sync_check/eqptSupC_POS.json b/tests/checks/standby_sup_sync_check/eqptSupC_POS.json similarity index 100% rename from tests/standby_sup_sync_check/eqptSupC_POS.json rename to tests/checks/standby_sup_sync_check/eqptSupC_POS.json diff --git a/tests/checks/standby_sup_sync_check/test_standby_sup_sync_check.py b/tests/checks/standby_sup_sync_check/test_standby_sup_sync_check.py new file mode 100644 index 00000000..7268aff0 --- /dev/null +++ b/tests/checks/standby_sup_sync_check/test_standby_sup_sync_check.py @@ -0,0 +1,153 @@ +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__)) + +test_function = "standby_sup_sync_check" + +# icurl queries +eqptSupC_api = "eqptSupC.json" +eqptSupC_api += '?query-target-filter=eq(eqptSupC.rdSt,"standby")' + +""" +Bug cversion/tversion matrix based on image size + +4.2(7t)+ - fixed versions LT 2 Gigs: 4.2(7t)+ +5.2(5d)+ - fixed versions LT 2 Gigs: 5.2(7f)+ +5.3(1d)+ - fixed versions LT 2 Gigs: 5.3(1d)+ +6.0(1g)+ - fixed versions LT 2 Gigs: 6.0(1g), 6.0(1j). 32-bit only: 6.0(2h), 6.0(2j). 64-bit: NONE +6.1(1f)+ - fixed versions LT 2 Gigs: NONE +""" + + +@pytest.mark.parametrize( + "icurl_outputs, cversion, tversion, expected_result", + [ + # NO TVERSION - MANUAL + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "5.2(1a)", + None, + script.MANUAL, + ), + # CVERSION 4.2 + # cversion 4.2 -nofix, tversion 4.2 -fix LT 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "4.2(7a)", + "4.2(8d)", + script.PASS, + ), + # cversion 4.2 -nofix, tversion 5.2 -fix but over 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "4.2(7a)", + "5.2(5d)", + script.FAIL_UF, + ), + # cversion 4.2 -nofix, tversion 5.2 -fix and LT 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "4.2(7a)", + "5.2(7f)", + script.PASS, + ), + # cversion 4.2 -nofix, tversion 5.3 -fix and LT 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "4.2(7a)", + "5.3(1d)", + script.PASS, + ), + # cversion 4.2 -nofix, tversion 6.0 -fix and LT 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "4.2(7a)", + "6.0(8d)", + script.FAIL_UF, + ), + # cversion 4.2 -nofix, tversion 6.1 -fix and LT 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "4.2(7a)", + "6.1(1f)", + script.FAIL_UF, + ), + # cversion 4.2 -fix, tversion 6.0 -fix but over 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "4.2(7t)", + "6.0(6h)", + script.PASS, + ), + # cversion 4.2 -fix, tversion 6.1 -fix but over 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "4.2(7t)", + "6.1(1f)", + script.PASS, + ), + # CVERSION 5.2 + # cversion 5.2 -nofix, tversion 5.2 -fix but over 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "5.2(4a)", + "5.2(7a)", + script.FAIL_UF, + ), + # cversion 5.2 -nofix, tversion 5.2 -fix LT 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "5.2(4a)", + "5.2(7f)", + script.PASS, + ), + # cversion 5.2 -nofix, tversion 5.3 -fix LT 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "5.2(4a)", + "5.3(1d)", + script.PASS, + ), + # cversion 5.2 -nofix, tversion 6.0 -fix but over 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "5.2(4a)", + "6.0(8d)", + script.FAIL_UF, + ), + # cversion 5.2 -nofix, tversion 6.1 -fix but over 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "5.2(4a)", + "6.1(1f)", + script.FAIL_UF, + ), + # cversion 5.2 -fix, tversion 6.1 -fix but over 2G + ( + {eqptSupC_api: read_data(dir, "eqptSupC_POS.json")}, + "5.2(5d)", + "6.1(1f)", + script.PASS, + ), + # NO STANDBY SUPS + ( + {eqptSupC_api: read_data(dir, "eqptSupC_NEG.json")}, + "4.2(7a)", + "6.1(1f)", + script.PASS, + ), + ], +) +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None + ) + assert result.result == expected_result diff --git a/tests/static_route_overlap_check/fvRsCtx.json b/tests/checks/static_route_overlap_check/fvRsCtx.json similarity index 100% rename from tests/static_route_overlap_check/fvRsCtx.json rename to tests/checks/static_route_overlap_check/fvRsCtx.json diff --git a/tests/static_route_overlap_check/fvSubnet.json b/tests/checks/static_route_overlap_check/fvSubnet.json similarity index 100% rename from tests/static_route_overlap_check/fvSubnet.json rename to tests/checks/static_route_overlap_check/fvSubnet.json diff --git a/tests/static_route_overlap_check/ipRouteP_empty.json b/tests/checks/static_route_overlap_check/ipRouteP_empty.json similarity index 100% rename from tests/static_route_overlap_check/ipRouteP_empty.json rename to tests/checks/static_route_overlap_check/ipRouteP_empty.json diff --git a/tests/static_route_overlap_check/ipRouteP_neg.json b/tests/checks/static_route_overlap_check/ipRouteP_neg.json similarity index 100% rename from tests/static_route_overlap_check/ipRouteP_neg.json rename to tests/checks/static_route_overlap_check/ipRouteP_neg.json diff --git a/tests/static_route_overlap_check/ipRouteP_pos.json b/tests/checks/static_route_overlap_check/ipRouteP_pos.json similarity index 100% rename from tests/static_route_overlap_check/ipRouteP_pos.json rename to tests/checks/static_route_overlap_check/ipRouteP_pos.json diff --git a/tests/static_route_overlap_check/l3extRsEctx.json b/tests/checks/static_route_overlap_check/l3extRsEctx.json similarity index 100% rename from tests/static_route_overlap_check/l3extRsEctx.json rename to tests/checks/static_route_overlap_check/l3extRsEctx.json diff --git a/tests/checks/static_route_overlap_check/test_static_route_overlap_check.py b/tests/checks/static_route_overlap_check/test_static_route_overlap_check.py new file mode 100644 index 00000000..21333137 --- /dev/null +++ b/tests/checks/static_route_overlap_check/test_static_route_overlap_check.py @@ -0,0 +1,91 @@ +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__)) + +test_function = "static_route_overlap_check" + +# icurl queries +staticRoutes = 'ipRouteP.json?query-target-filter=and(wcard(ipRouteP.dn,"/32"))' +staticroute_vrf = "l3extRsEctx.json" +bds_in_vrf = "fvRsCtx.json" +subnets_in_bd = "fvSubnet.json" + + +@pytest.mark.parametrize( + "icurl_outputs, cversion, tversion, expected_result", + [ + # FAIL = AFFECTED VERSION + AFFECTED MO + ( + { + staticRoutes: read_data(dir, "ipRouteP_pos.json"), + staticroute_vrf: read_data(dir, "l3extRsEctx.json"), + bds_in_vrf: read_data(dir, "fvRsCtx.json"), + subnets_in_bd: read_data(dir, "fvSubnet.json"), + }, + "4.2(7f)", + "5.2(4d)", + script.FAIL_O, + ), + # FAIL = AFFECTED VERSION + AFFECTED MO + ( + { + staticRoutes: read_data(dir, "ipRouteP_pos.json"), + staticroute_vrf: read_data(dir, "l3extRsEctx.json"), + bds_in_vrf: read_data(dir, "fvRsCtx.json"), + subnets_in_bd: read_data(dir, "fvSubnet.json"), + }, + "5.1(1a)", + "5.2(4d)", + script.FAIL_O, + ), + # PASS = AFFECTED VERSION + NON-AFFECTED MO + ( + { + staticRoutes: read_data(dir, "ipRouteP_neg.json"), + staticroute_vrf: read_data(dir, "l3extRsEctx.json"), + bds_in_vrf: read_data(dir, "fvRsCtx.json"), + subnets_in_bd: read_data(dir, "fvSubnet.json"), + }, + "4.2(7f)", + "5.2(4d)", + script.PASS, + ), + # PASS = AFFECTED VERSION + AFFECTED MO NON EXISTING + ( + { + staticRoutes: read_data(dir, "ipRouteP_empty.json"), + staticroute_vrf: read_data(dir, "l3extRsEctx.json"), + bds_in_vrf: read_data(dir, "fvRsCtx.json"), + subnets_in_bd: read_data(dir, "fvSubnet.json"), + }, + "4.2(7f)", + "5.2(4d)", + script.PASS, + ), + # PASS = NON-AFFECTED VERSION + AFFECTED MO + ( + { + staticRoutes: read_data(dir, "ipRouteP_pos.json"), + staticroute_vrf: read_data(dir, "l3extRsEctx.json"), + bds_in_vrf: read_data(dir, "fvRsCtx.json"), + subnets_in_bd: read_data(dir, "fvSubnet.json"), + }, + "4.2(7f)", + "5.2(6e)", + script.PASS, + ), + ], +) +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion), + ) + assert result.result == expected_result diff --git a/tests/subnet_scope_check/fvAEPg_empty.json b/tests/checks/subnet_scope_check/fvAEPg_empty.json similarity index 100% rename from tests/subnet_scope_check/fvAEPg_empty.json rename to tests/checks/subnet_scope_check/fvAEPg_empty.json diff --git a/tests/subnet_scope_check/fvAEPg_neg.json b/tests/checks/subnet_scope_check/fvAEPg_neg.json similarity index 100% rename from tests/subnet_scope_check/fvAEPg_neg.json rename to tests/checks/subnet_scope_check/fvAEPg_neg.json diff --git a/tests/subnet_scope_check/fvAEPg_pos.json b/tests/checks/subnet_scope_check/fvAEPg_pos.json similarity index 100% rename from tests/subnet_scope_check/fvAEPg_pos.json rename to tests/checks/subnet_scope_check/fvAEPg_pos.json diff --git a/tests/subnet_scope_check/fvBD.json b/tests/checks/subnet_scope_check/fvBD.json similarity index 100% rename from tests/subnet_scope_check/fvBD.json rename to tests/checks/subnet_scope_check/fvBD.json diff --git a/tests/subnet_scope_check/fvRsBd.json b/tests/checks/subnet_scope_check/fvRsBd.json similarity index 100% rename from tests/subnet_scope_check/fvRsBd.json rename to tests/checks/subnet_scope_check/fvRsBd.json diff --git a/tests/checks/subnet_scope_check/test_subnet_scope_check.py b/tests/checks/subnet_scope_check/test_subnet_scope_check.py new file mode 100644 index 00000000..479d4809 --- /dev/null +++ b/tests/checks/subnet_scope_check/test_subnet_scope_check.py @@ -0,0 +1,74 @@ +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__)) + +test_function = "subnet_scope_check" + +# icurl queries +bd_api = "fvBD.json" +bd_api += "?rsp-subtree=children&rsp-subtree-class=fvSubnet&rsp-subtree-include=required" + +epg_api = "fvAEPg.json?" +epg_api += "rsp-subtree=children&rsp-subtree-class=fvSubnet&rsp-subtree-include=required" + + +@pytest.mark.parametrize( + "icurl_outputs, cversion, expected_result", + [ + ( + { + bd_api: read_data(dir, "fvBD.json"), + epg_api: read_data(dir, "fvAEPg_empty.json"), + "fvRsBd.json": read_data(dir, "fvRsBd.json"), + }, + "4.2(6a)", + script.NA, + ), + ( + { + bd_api: read_data(dir, "fvBD.json"), + epg_api: read_data(dir, "fvAEPg_pos.json"), + "fvRsBd.json": read_data(dir, "fvRsBd.json"), + }, + "4.2(6a)", + script.FAIL_O, + ), + ( + { + bd_api: read_data(dir, "fvBD.json"), + epg_api: read_data(dir, "fvAEPg_pos.json"), + "fvRsBd.json": read_data(dir, "fvRsBd.json"), + }, + "5.1(1a)", + script.FAIL_O, + ), + ( + { + bd_api: read_data(dir, "fvBD.json"), + epg_api: read_data(dir, "fvAEPg_neg.json"), + "fvRsBd.json": read_data(dir, "fvRsBd.json"), + }, + "5.1(1a)", + script.PASS, + ), + ( + { + bd_api: read_data(dir, "fvBD.json"), + epg_api: read_data(dir, "fvAEPg_neg.json"), + "fvRsBd.json": read_data(dir, "fvRsBd.json"), + }, + "5.2(8h)", + script.PASS, + ), + ], +) +def test_logic(run_check, mock_icurl, cversion, expected_result): + result = run_check(cversion=script.AciVersion(cversion)) + assert result.result == expected_result diff --git a/tests/sup_a_high_memory_check/eqptSupC_SUP_A.json b/tests/checks/sup_a_high_memory_check/eqptSupC_SUP_A.json similarity index 100% rename from tests/sup_a_high_memory_check/eqptSupC_SUP_A.json rename to tests/checks/sup_a_high_memory_check/eqptSupC_SUP_A.json diff --git a/tests/sup_a_high_memory_check/eqptSupC_SUP_A_Aplus.json b/tests/checks/sup_a_high_memory_check/eqptSupC_SUP_A_Aplus.json similarity index 100% rename from tests/sup_a_high_memory_check/eqptSupC_SUP_A_Aplus.json rename to tests/checks/sup_a_high_memory_check/eqptSupC_SUP_A_Aplus.json diff --git a/tests/sup_a_high_memory_check/eqptSupC_SUP_Aplus.json b/tests/checks/sup_a_high_memory_check/eqptSupC_SUP_Aplus.json similarity index 100% rename from tests/sup_a_high_memory_check/eqptSupC_SUP_Aplus.json rename to tests/checks/sup_a_high_memory_check/eqptSupC_SUP_Aplus.json diff --git a/tests/sup_a_high_memory_check/eqptSupC_no_SUP_A_Aplus.json b/tests/checks/sup_a_high_memory_check/eqptSupC_no_SUP_A_Aplus.json similarity index 100% rename from tests/sup_a_high_memory_check/eqptSupC_no_SUP_A_Aplus.json rename to tests/checks/sup_a_high_memory_check/eqptSupC_no_SUP_A_Aplus.json diff --git a/tests/sup_a_high_memory_check/test_sup_a_high_memory_check.py b/tests/checks/sup_a_high_memory_check/test_sup_a_high_memory_check.py similarity index 87% rename from tests/sup_a_high_memory_check/test_sup_a_high_memory_check.py rename to tests/checks/sup_a_high_memory_check/test_sup_a_high_memory_check.py index 80982357..17ce861c 100644 --- a/tests/sup_a_high_memory_check/test_sup_a_high_memory_check.py +++ b/tests/checks/sup_a_high_memory_check/test_sup_a_high_memory_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "sup_a_high_memory_check" # icurl queries eqptSupCs = "eqptSupC.json" @@ -55,6 +56,6 @@ ), ], ) -def test_logic(mock_icurl, tversion, expected_result): - result = script.sup_a_high_memory_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/sup_hwrev_check/eqptSpCmnBlk_NEG.json b/tests/checks/sup_hwrev_check/eqptSpCmnBlk_NEG.json similarity index 100% rename from tests/sup_hwrev_check/eqptSpCmnBlk_NEG.json rename to tests/checks/sup_hwrev_check/eqptSpCmnBlk_NEG.json diff --git a/tests/sup_hwrev_check/eqptSpCmnBlk_POS.json b/tests/checks/sup_hwrev_check/eqptSpCmnBlk_POS.json similarity index 100% rename from tests/sup_hwrev_check/eqptSpCmnBlk_POS.json rename to tests/checks/sup_hwrev_check/eqptSpCmnBlk_POS.json diff --git a/tests/sup_hwrev_check/test_sup_hwrev_check.py b/tests/checks/sup_hwrev_check/test_sup_hwrev_check.py similarity index 85% rename from tests/sup_hwrev_check/test_sup_hwrev_check.py rename to tests/checks/sup_hwrev_check/test_sup_hwrev_check.py index 5ad6baa9..c8094296 100644 --- a/tests/sup_hwrev_check/test_sup_hwrev_check.py +++ b/tests/checks/sup_hwrev_check/test_sup_hwrev_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "sup_hwrev_check" # icurl queries eqptSpCmnBlk = 'eqptSpCmnBlk.json?&query-target-filter=wcard(eqptSpromSupBlk.dn,"sup")' @@ -54,8 +55,9 @@ ), ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.sup_hwrev_check( - 1, 1, script.AciVersion(cversion), script.AciVersion(tversion) +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion), ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/switch_bootflash_usage_check/eqptcapacityFSPartition.json b/tests/checks/switch_bootflash_usage_check/eqptcapacityFSPartition.json similarity index 100% rename from tests/switch_bootflash_usage_check/eqptcapacityFSPartition.json rename to tests/checks/switch_bootflash_usage_check/eqptcapacityFSPartition.json diff --git a/tests/switch_bootflash_usage_check/maintUpgJob_not_downloaded.json b/tests/checks/switch_bootflash_usage_check/maintUpgJob_not_downloaded.json similarity index 100% rename from tests/switch_bootflash_usage_check/maintUpgJob_not_downloaded.json rename to tests/checks/switch_bootflash_usage_check/maintUpgJob_not_downloaded.json diff --git a/tests/switch_bootflash_usage_check/maintUpgJob_old_ver_no_prop.json b/tests/checks/switch_bootflash_usage_check/maintUpgJob_old_ver_no_prop.json similarity index 100% rename from tests/switch_bootflash_usage_check/maintUpgJob_old_ver_no_prop.json rename to tests/checks/switch_bootflash_usage_check/maintUpgJob_old_ver_no_prop.json diff --git a/tests/switch_bootflash_usage_check/maintUpgJob_pre_downloaded.json b/tests/checks/switch_bootflash_usage_check/maintUpgJob_pre_downloaded.json similarity index 100% rename from tests/switch_bootflash_usage_check/maintUpgJob_pre_downloaded.json rename to tests/checks/switch_bootflash_usage_check/maintUpgJob_pre_downloaded.json diff --git a/tests/checks/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py b/tests/checks/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py new file mode 100644 index 00000000..0b85f163 --- /dev/null +++ b/tests/checks/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py @@ -0,0 +1,54 @@ +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__)) + +test_function = "switch_bootflash_usage_check" + +# icurl queries +partitions = "eqptcapacityFSPartition.json" +partitions += '?query-target-filter=eq(eqptcapacityFSPartition.path,"/bootflash")' + +download_sts = "maintUpgJob.json" +download_sts += '?query-target-filter=and(eq(maintUpgJob.dnldStatus,"downloaded")' +download_sts += ',eq(maintUpgJob.desiredVersion,"n9000-16.0(2h)"))' + + +@pytest.mark.parametrize( + "icurl_outputs, tversion, expected_result", + [ + ( + { + partitions: read_data(dir, "eqptcapacityFSPartition.json"), + download_sts: read_data(dir, "maintUpgJob_not_downloaded.json"), + }, + "6.0(2h)", + script.FAIL_UF, + ), + ( + { + partitions: read_data(dir, "eqptcapacityFSPartition.json"), + download_sts: read_data(dir, "maintUpgJob_pre_downloaded.json"), + }, + "6.0(2h)", + script.PASS, + ), + ( + { + partitions: read_data(dir, "eqptcapacityFSPartition.json"), + download_sts: read_data(dir, "maintUpgJob_old_ver_no_prop.json"), + }, + "6.0(2h)", + script.FAIL_UF, + ), + ], +) +def test_logic(run_check, mock_icurl, tversion, expected_result): + result = run_check(tversion=script.AciVersion(tversion)) + assert result.result == expected_result diff --git a/tests/telemetryStatsServerP_object_check/telemetryStatsServerP_neg.json b/tests/checks/telemetryStatsServerP_object_check/telemetryStatsServerP_neg.json similarity index 100% rename from tests/telemetryStatsServerP_object_check/telemetryStatsServerP_neg.json rename to tests/checks/telemetryStatsServerP_object_check/telemetryStatsServerP_neg.json diff --git a/tests/telemetryStatsServerP_object_check/telemetryStatsServerP_pos.json b/tests/checks/telemetryStatsServerP_object_check/telemetryStatsServerP_pos.json similarity index 100% rename from tests/telemetryStatsServerP_object_check/telemetryStatsServerP_pos.json rename to tests/checks/telemetryStatsServerP_object_check/telemetryStatsServerP_pos.json diff --git a/tests/telemetryStatsServerP_object_check/test_telemetryStatsServerP_object_check.py b/tests/checks/telemetryStatsServerP_object_check/test_telemetryStatsServerP_object_check.py similarity index 86% rename from tests/telemetryStatsServerP_object_check/test_telemetryStatsServerP_object_check.py rename to tests/checks/telemetryStatsServerP_object_check/test_telemetryStatsServerP_object_check.py index 9c234348..10d6fef9 100644 --- a/tests/telemetryStatsServerP_object_check/test_telemetryStatsServerP_object_check.py +++ b/tests/checks/telemetryStatsServerP_object_check/test_telemetryStatsServerP_object_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "telemetryStatsServerP_object_check" # icurl queries telemetryStatsServerPs = "telemetryStatsServerP.json" @@ -79,11 +80,9 @@ ), ], ) -def test_logic(mock_icurl, sw_cversion, tversion, expected_result): - result = script.telemetryStatsServerP_object_check( - 1, - 1, - script.AciVersion(sw_cversion), - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, sw_cversion, tversion, expected_result): + result = run_check( + sw_cversion=script.AciVersion(sw_cversion), + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/tep-to-tep_atomic_counter_check/dbgAcPath_max.json b/tests/checks/tep-to-tep_atomic_counter_check/dbgAcPath_max.json similarity index 100% rename from tests/tep-to-tep_atomic_counter_check/dbgAcPath_max.json rename to tests/checks/tep-to-tep_atomic_counter_check/dbgAcPath_max.json diff --git a/tests/tep-to-tep_atomic_counter_check/dbgAcPath_na.json b/tests/checks/tep-to-tep_atomic_counter_check/dbgAcPath_na.json similarity index 100% rename from tests/tep-to-tep_atomic_counter_check/dbgAcPath_na.json rename to tests/checks/tep-to-tep_atomic_counter_check/dbgAcPath_na.json diff --git a/tests/tep-to-tep_atomic_counter_check/dbgAcPath_pass.json b/tests/checks/tep-to-tep_atomic_counter_check/dbgAcPath_pass.json similarity index 100% rename from tests/tep-to-tep_atomic_counter_check/dbgAcPath_pass.json rename to tests/checks/tep-to-tep_atomic_counter_check/dbgAcPath_pass.json diff --git a/tests/tep-to-tep_atomic_counter_check/test_tep_to_tep_ac_count_check.py b/tests/checks/tep-to-tep_atomic_counter_check/test_tep_to_tep_ac_count_check.py similarity index 71% rename from tests/tep-to-tep_atomic_counter_check/test_tep_to_tep_ac_count_check.py rename to tests/checks/tep-to-tep_atomic_counter_check/test_tep_to_tep_ac_count_check.py index 447adaf1..6db17907 100644 --- a/tests/tep-to-tep_atomic_counter_check/test_tep_to_tep_ac_count_check.py +++ b/tests/checks/tep-to-tep_atomic_counter_check/test_tep_to_tep_ac_count_check.py @@ -9,6 +9,8 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "tep_to_tep_ac_counter_check" + # icurl queries atomic_counter_api = 'dbgAcPath.json' atomic_counter_api += '?rsp-subtree-include=count' @@ -17,26 +19,23 @@ @pytest.mark.parametrize( "icurl_outputs, expected_result", [ - ##FAILING = COUNT > 1600 + # FAILING = COUNT > 1600 ( - {atomic_counter_api: read_data(dir, "dbgAcPath_max.json"), - }, + {atomic_counter_api: read_data(dir, "dbgAcPath_max.json")}, script.FAIL_UF, ), - ##PASSING = COUNT > 0 < = 1600 + # PASSING = COUNT > 0 < = 1600 ( - {atomic_counter_api: read_data(dir, "dbgAcPath_pass.json"), - }, + {atomic_counter_api: read_data(dir, "dbgAcPath_pass.json")}, script.PASS, ), - ##N/A = COUNT EQUAL 0 + # N/A = COUNT EQUAL 0 ( - {atomic_counter_api: read_data(dir, "dbgAcPath_na.json"), - }, + {atomic_counter_api: read_data(dir, "dbgAcPath_na.json")}, script.NA, ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.tep_to_tep_ac_counter_check(1, 1) - assert result == expected_result \ No newline at end of file +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/unsupported_fec_configuration_ex_check/test_unsupported_fec_configuration_ex_check.py b/tests/checks/unsupported_fec_configuration_ex_check/test_unsupported_fec_configuration_ex_check.py similarity index 82% rename from tests/unsupported_fec_configuration_ex_check/test_unsupported_fec_configuration_ex_check.py rename to tests/checks/unsupported_fec_configuration_ex_check/test_unsupported_fec_configuration_ex_check.py index 0990c762..d9fd9a86 100644 --- a/tests/unsupported_fec_configuration_ex_check/test_unsupported_fec_configuration_ex_check.py +++ b/tests/checks/unsupported_fec_configuration_ex_check/test_unsupported_fec_configuration_ex_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "unsupported_fec_configuration_ex_check" # icurl queries topSystems = 'topSystem.json' @@ -16,6 +17,7 @@ topSystems += '&rsp-subtree-filter=or(eq(l1PhysIf.fecMode,"ieee-rs-fec"),eq(l1PhysIf.fecMode,"cons16-rs-fec"),eq(eqptCh.model,"N9K-C93180YC-EX"))' topSystems += '&rsp-subtree-include=required' + @pytest.mark.parametrize( "icurl_outputs, sw_cversion, tversion, expected_result", [ @@ -63,11 +65,9 @@ ), ], ) -def test_logic(mock_icurl, sw_cversion, tversion, expected_result): - result = script.unsupported_fec_configuration_ex_check( - 1, - 1, - script.AciVersion(sw_cversion) if sw_cversion else None, - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, sw_cversion, tversion, expected_result): + result = run_check( + sw_cversion=script.AciVersion(sw_cversion) if sw_cversion else None, + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result \ No newline at end of file + assert result.result == expected_result diff --git a/tests/unsupported_fec_configuration_ex_check/topSystem_neg.json b/tests/checks/unsupported_fec_configuration_ex_check/topSystem_neg.json similarity index 100% rename from tests/unsupported_fec_configuration_ex_check/topSystem_neg.json rename to tests/checks/unsupported_fec_configuration_ex_check/topSystem_neg.json diff --git a/tests/unsupported_fec_configuration_ex_check/topSystem_pos.json b/tests/checks/unsupported_fec_configuration_ex_check/topSystem_pos.json similarity index 100% rename from tests/unsupported_fec_configuration_ex_check/topSystem_pos.json rename to tests/checks/unsupported_fec_configuration_ex_check/topSystem_pos.json diff --git a/tests/uplink_limit_check/eqptPortP_NEG.json b/tests/checks/uplink_limit_check/eqptPortP_NEG.json similarity index 100% rename from tests/uplink_limit_check/eqptPortP_NEG.json rename to tests/checks/uplink_limit_check/eqptPortP_NEG.json diff --git a/tests/uplink_limit_check/eqptPortP_POS.json b/tests/checks/uplink_limit_check/eqptPortP_POS.json similarity index 100% rename from tests/uplink_limit_check/eqptPortP_POS.json rename to tests/checks/uplink_limit_check/eqptPortP_POS.json diff --git a/tests/uplink_limit_check/eqptPortP_empty.json b/tests/checks/uplink_limit_check/eqptPortP_empty.json similarity index 100% rename from tests/uplink_limit_check/eqptPortP_empty.json rename to tests/checks/uplink_limit_check/eqptPortP_empty.json diff --git a/tests/uplink_limit_check/test_uplink_limit_check.py b/tests/checks/uplink_limit_check/test_uplink_limit_check.py similarity index 82% rename from tests/uplink_limit_check/test_uplink_limit_check.py rename to tests/checks/uplink_limit_check/test_uplink_limit_check.py index 0cf9dc82..128ae316 100644 --- a/tests/uplink_limit_check/test_uplink_limit_check.py +++ b/tests/checks/uplink_limit_check/test_uplink_limit_check.py @@ -8,6 +8,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "uplink_limit_check" # icurl queries eqptPortP = 'eqptPortP.json?query-target-filter=eq(eqptPortP.ctrl,"uplink")' @@ -50,8 +51,9 @@ ) ], ) -def test_logic(mock_icurl, cver, tver, expected_result): - result = script.uplink_limit_check( - 1, 1, script.AciVersion(cver), script.AciVersion(tver) +def test_logic(run_check, mock_icurl, cver, tver, expected_result): + result = run_check( + cversion=script.AciVersion(cver), + tversion=script.AciVersion(tver), ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/validate_32_64_bit_image_check/firmwareFirmware_empty.json b/tests/checks/validate_32_64_bit_image_check/firmwareFirmware_empty.json similarity index 100% rename from tests/validate_32_64_bit_image_check/firmwareFirmware_empty.json rename to tests/checks/validate_32_64_bit_image_check/firmwareFirmware_empty.json diff --git a/tests/validate_32_64_bit_image_check/firmwareFirmware_neg.json b/tests/checks/validate_32_64_bit_image_check/firmwareFirmware_neg.json similarity index 100% rename from tests/validate_32_64_bit_image_check/firmwareFirmware_neg.json rename to tests/checks/validate_32_64_bit_image_check/firmwareFirmware_neg.json diff --git a/tests/validate_32_64_bit_image_check/firmwareFirmware_pos.json b/tests/checks/validate_32_64_bit_image_check/firmwareFirmware_pos.json similarity index 100% rename from tests/validate_32_64_bit_image_check/firmwareFirmware_pos.json rename to tests/checks/validate_32_64_bit_image_check/firmwareFirmware_pos.json diff --git a/tests/validate_32_64_bit_image_check/firmwareFirmware_pos2.json b/tests/checks/validate_32_64_bit_image_check/firmwareFirmware_pos2.json similarity index 100% rename from tests/validate_32_64_bit_image_check/firmwareFirmware_pos2.json rename to tests/checks/validate_32_64_bit_image_check/firmwareFirmware_pos2.json diff --git a/tests/validate_32_64_bit_image_check/firmwareFirmware_pos3.json b/tests/checks/validate_32_64_bit_image_check/firmwareFirmware_pos3.json similarity index 100% rename from tests/validate_32_64_bit_image_check/firmwareFirmware_pos3.json rename to tests/checks/validate_32_64_bit_image_check/firmwareFirmware_pos3.json diff --git a/tests/validate_32_64_bit_image_check/firmwareFirmware_pos4.json b/tests/checks/validate_32_64_bit_image_check/firmwareFirmware_pos4.json similarity index 100% rename from tests/validate_32_64_bit_image_check/firmwareFirmware_pos4.json rename to tests/checks/validate_32_64_bit_image_check/firmwareFirmware_pos4.json diff --git a/tests/validate_32_64_bit_image_check/test_validate_32_64_bit_image_check.py b/tests/checks/validate_32_64_bit_image_check/test_validate_32_64_bit_image_check.py similarity index 59% rename from tests/validate_32_64_bit_image_check/test_validate_32_64_bit_image_check.py rename to tests/checks/validate_32_64_bit_image_check/test_validate_32_64_bit_image_check.py index 75eac752..2371d5ad 100644 --- a/tests/validate_32_64_bit_image_check/test_validate_32_64_bit_image_check.py +++ b/tests/checks/validate_32_64_bit_image_check/test_validate_32_64_bit_image_check.py @@ -9,97 +9,88 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "validate_32_64_bit_image_check" + # icurl queries -firmware_60_api = 'firmwareFirmware.json' -firmware_60_api += '?query-target-filter=eq(firmwareFirmware.fullVersion,"n9000-16.0(3e)")' +firmware_60_api = "firmwareFirmware.json" +firmware_60_api += '?query-target-filter=eq(firmwareFirmware.fullVersion,"n9000-16.0(3e)")' # icurl queries -firmware_52_api = 'firmwareFirmware.json' -firmware_52_api += '?query-target-filter=eq(firmwareFirmware.fullVersion,"n9000-15.2(7d)")' +firmware_52_api = "firmwareFirmware.json" +firmware_52_api += '?query-target-filter=eq(firmwareFirmware.fullVersion,"n9000-15.2(7d)")' + @pytest.mark.parametrize( "icurl_outputs, cversion, tversion, expected_result", [ - ## NO TVERSION - MANUAL + # NO TVERSION - MANUAL ( - {firmware_60_api: read_data(dir, "firmwareFirmware_pos.json"), - }, + {firmware_60_api: read_data(dir, "firmwareFirmware_pos.json")}, "5.2(1a)", None, script.MANUAL, ), - ## APIC not yet upgraded to 6.0(2)+ - POST + # APIC not yet upgraded to 6.0(2)+ - POST ( - {firmware_60_api: read_data(dir, "firmwareFirmware_pos.json"), - }, + {firmware_60_api: read_data(dir, "firmwareFirmware_pos.json")}, "5.2(1a)", "6.0(3e)", script.POST, ), - ## FAILING = AFFECTED VERSION + ONLY 64 BIT Image + # FAILING = AFFECTED VERSION + ONLY 64 BIT Image ( - {firmware_60_api: read_data(dir, "firmwareFirmware_pos.json"), - }, + {firmware_60_api: read_data(dir, "firmwareFirmware_pos.json")}, "6.0(3e)", "6.0(3e)", script.FAIL_UF, ), - ## FAILING = AFFECTED VERSION + Images were uploaded before upgrade + # FAILING = AFFECTED VERSION + Images were uploaded before upgrade ( - {firmware_60_api: read_data(dir, "firmwareFirmware_pos2.json"), - }, + {firmware_60_api: read_data(dir, "firmwareFirmware_pos2.json")}, "6.0(3e)", "6.0(3e)", script.FAIL_UF, ), - ## FAILING = AFFECTED VERSION + 32-bit image shows NA + # FAILING = AFFECTED VERSION + 32-bit image shows NA ( - {firmware_60_api: read_data(dir, "firmwareFirmware_pos3.json"), - }, + {firmware_60_api: read_data(dir, "firmwareFirmware_pos3.json")}, "6.0(3e)", "6.0(3e)", script.FAIL_UF, ), - ## FAILING = AFFECTED VERSION + 64-bit image shows NA + # FAILING = AFFECTED VERSION + 64-bit image shows NA ( - {firmware_60_api: read_data(dir, "firmwareFirmware_pos4.json"), - }, + {firmware_60_api: read_data(dir, "firmwareFirmware_pos4.json")}, "6.0(3e)", "6.0(3e)", script.FAIL_UF, ), - ## FAILING = AFFECTED VERSION + AFFECTED MO NON EXISTING + # FAILING = AFFECTED VERSION + AFFECTED MO NON EXISTING ( - {firmware_60_api: read_data(dir, "firmwareFirmware_empty.json"), - }, + {firmware_60_api: read_data(dir, "firmwareFirmware_empty.json")}, "6.0(3e)", "6.0(3e)", script.FAIL_UF, ), - ## PASSING = AFFECTED VERSION + NON-AFFECTED MO + # PASSING = AFFECTED VERSION + NON-AFFECTED MO ( - {firmware_60_api: read_data(dir, "firmwareFirmware_neg.json"), - }, + {firmware_60_api: read_data(dir, "firmwareFirmware_neg.json")}, "6.0(3e)", "6.0(3e)", script.PASS, ), - ## PASSING = NON-AFFECTED VERSION + AFFECTED MO + # PASSING = NON-AFFECTED VERSION + AFFECTED MO ( - {firmware_52_api: read_data(dir, "firmwareFirmware_empty.json"), - }, + {firmware_52_api: read_data(dir, "firmwareFirmware_empty.json")}, "5.2(1a)", "5.2(7d)", script.NA, ), - ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.validate_32_64_bit_image_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None ) - assert result == expected_result \ No newline at end of file + assert result.result == expected_result diff --git a/tests/vmm_active_uplinks_check/fvUplinkOrderCont_neg.json b/tests/checks/vmm_active_uplinks_check/fvUplinkOrderCont_neg.json similarity index 100% rename from tests/vmm_active_uplinks_check/fvUplinkOrderCont_neg.json rename to tests/checks/vmm_active_uplinks_check/fvUplinkOrderCont_neg.json diff --git a/tests/vmm_active_uplinks_check/fvUplinkOrderCont_not_exist.json b/tests/checks/vmm_active_uplinks_check/fvUplinkOrderCont_not_exist.json similarity index 100% rename from tests/vmm_active_uplinks_check/fvUplinkOrderCont_not_exist.json rename to tests/checks/vmm_active_uplinks_check/fvUplinkOrderCont_not_exist.json diff --git a/tests/vmm_active_uplinks_check/fvUplinkOrderCont_pos.json b/tests/checks/vmm_active_uplinks_check/fvUplinkOrderCont_pos.json similarity index 100% rename from tests/vmm_active_uplinks_check/fvUplinkOrderCont_pos.json rename to tests/checks/vmm_active_uplinks_check/fvUplinkOrderCont_pos.json diff --git a/tests/vmm_active_uplinks_check/test_vmm_active_uplinks_check.py b/tests/checks/vmm_active_uplinks_check/test_vmm_active_uplinks_check.py similarity index 78% rename from tests/vmm_active_uplinks_check/test_vmm_active_uplinks_check.py rename to tests/checks/vmm_active_uplinks_check/test_vmm_active_uplinks_check.py index 846f3438..5920efd5 100644 --- a/tests/vmm_active_uplinks_check/test_vmm_active_uplinks_check.py +++ b/tests/checks/vmm_active_uplinks_check/test_vmm_active_uplinks_check.py @@ -9,9 +9,10 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "vmm_active_uplinks_check" # icurl queries -uplink_api = 'fvUplinkOrderCont.json' +uplink_api = "fvUplinkOrderCont.json" uplink_api += '?query-target-filter=eq(fvUplinkOrderCont.active,"")' @@ -32,6 +33,6 @@ ), ], ) -def test_logic(mock_icurl, expected_result): - result = script.vmm_active_uplinks_check(1, 1) - assert result == expected_result +def test_logic(run_check, mock_icurl, expected_result): + result = run_check() + assert result.result == expected_result diff --git a/tests/vpc_paired_switches_check/test_vpc_paired_switches_check.py b/tests/checks/vpc_paired_switches_check/test_vpc_paired_switches_check.py similarity index 78% rename from tests/vpc_paired_switches_check/test_vpc_paired_switches_check.py rename to tests/checks/vpc_paired_switches_check/test_vpc_paired_switches_check.py index 36191372..2b827248 100644 --- a/tests/vpc_paired_switches_check/test_vpc_paired_switches_check.py +++ b/tests/checks/vpc_paired_switches_check/test_vpc_paired_switches_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "vpc_paired_switches_check" # icurl queries topSystems = "topSystem.json" @@ -31,6 +32,6 @@ ), ], ) -def test_logic(mock_icurl, vpc_node_ids, expected_result): - result = script.vpc_paired_switches_check(1, 1, vpc_node_ids=vpc_node_ids) - assert result == expected_result +def test_logic(run_check, mock_icurl, vpc_node_ids, expected_result): + result = run_check(vpc_node_ids=vpc_node_ids) + assert result.result == expected_result diff --git a/tests/vpc_paired_switches_check/topSystem.json b/tests/checks/vpc_paired_switches_check/topSystem.json similarity index 100% rename from tests/vpc_paired_switches_check/topSystem.json rename to tests/checks/vpc_paired_switches_check/topSystem.json diff --git a/tests/vzany_vzany_service_epg_check/test_vzany_vzany_service_epg_check.py b/tests/checks/vzany_vzany_service_epg_check/test_vzany_vzany_service_epg_check.py similarity index 92% rename from tests/vzany_vzany_service_epg_check/test_vzany_vzany_service_epg_check.py rename to tests/checks/vzany_vzany_service_epg_check/test_vzany_vzany_service_epg_check.py index 91072c23..44f7f1ca 100644 --- a/tests/vzany_vzany_service_epg_check/test_vzany_vzany_service_epg_check.py +++ b/tests/checks/vzany_vzany_service_epg_check/test_vzany_vzany_service_epg_check.py @@ -9,6 +9,7 @@ log = logging.getLogger(__name__) dir = os.path.dirname(os.path.abspath(__file__)) +test_function = "vzany_vzany_service_epg_check" # icurl queries vzRsSubjGraphAtts = "vzRsSubjGraphAtt.json" @@ -120,11 +121,9 @@ ), ], ) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.vzany_vzany_service_epg_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None, +def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + tversion=script.AciVersion(tversion) if tversion else None, ) - assert result == expected_result + assert result.result == expected_result diff --git a/tests/vzany_vzany_service_epg_check/vzRsSubjGraphAtt.json b/tests/checks/vzany_vzany_service_epg_check/vzRsSubjGraphAtt.json similarity index 100% rename from tests/vzany_vzany_service_epg_check/vzRsSubjGraphAtt.json rename to tests/checks/vzany_vzany_service_epg_check/vzRsSubjGraphAtt.json diff --git a/tests/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_cons_diff_VRFs.json b/tests/checks/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_cons_diff_VRFs.json similarity index 100% rename from tests/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_cons_diff_VRFs.json rename to tests/checks/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_cons_diff_VRFs.json diff --git a/tests/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_only.json b/tests/checks/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_only.json similarity index 100% rename from tests/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_only.json rename to tests/checks/vzany_vzany_service_epg_check/vzRtAny_vzAny_prov_only.json diff --git a/tests/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny.json b/tests/checks/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny.json similarity index 100% rename from tests/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny.json rename to tests/checks/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny.json diff --git a/tests/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny_2_VRFs.json b/tests/checks/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny_2_VRFs.json similarity index 100% rename from tests/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny_2_VRFs.json rename to tests/checks/vzany_vzany_service_epg_check/vzRtAny_vzAny_vzAny_2_VRFs.json diff --git a/tests/conftest.py b/tests/conftest.py index 92ba9a81..750796bd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import pytest import logging import importlib -from subprocess import CalledProcessError +from itertools import product TEST_DIR = os.path.dirname(os.path.abspath(__file__)) PROJECT_DIR = os.path.dirname(TEST_DIR) @@ -16,7 +16,7 @@ @pytest.fixture(scope="session", autouse=True) def init(): - script.initialize() + script.init_system() @pytest.fixture @@ -116,116 +116,234 @@ def _mock_icurl(apitype, query, page=0, page_size=100000): else: data = {"totalCount": output["totalCount"], "imdata": []} - script._icurl_error_handler(data['imdata']) + script._icurl_error_handler(data["imdata"]) return data monkeypatch.setattr(script, "_icurl", _mock_icurl) @pytest.fixture -def conn_failure(): - return False - - -@pytest.fixture -def conn_cmds(): - ''' - Set of test parameters for mocked `Connection.cmd()`. - - ex) - ``` - { - <apic_ip>: [{ - "cmd": "ls -aslh /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin", - "output": """\ - ls -aslh /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin - 6.1G -rwxr-xr-x 1 root root 6.1G Apr 3 16:36 /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin - f2-apic1# - """, - "exception": None - }] +def expected_common_data(request): + data = { # default + "username": "admin", + "password": "mypassword", + "cversion": script.AciVersion("6.1(1a)"), + "tversion": script.AciVersion("6.2(1a)"), + "sw_cversion": script.AciVersion("6.0(9d)"), + "vpc_node_ids": ["101", "102"], } - ``` - - The real output from `Connection.cmd()` (i.e. `Connection.output`) contains many ANSI characters. In this fixture, those characters are not considered. - ''' - return {} - - -class MockConnection(script.Connection): - conn_failure = False - conn_cmds = None - - def connect(self): - """ - `Connection.connect()` is just instantiating `pexepect.spawn()` which does not - initiate the SSH connection yet. Not exception likely happens here. - """ - if self.conn_failure: - raise Exception("Simulated exception at connect()") - - def cmd(self, command, **kargs): - """ - `Connection.cmd()` initiates the SSH connection (if not done yet) and sends the command. - Each check typically has multiple `cmd()` with different commands. - To cover that, this mock func uses a dictionary `conn_cmds` as the test data. - """ - _conn_cmds = self.conn_cmds[self.hostname] - for conn_cmd in _conn_cmds: - if command == conn_cmd["cmd"]: - if conn_cmd["exception"]: - raise conn_cmd["exception"] - self.output = conn_cmd["output"] - break + param = getattr(request, "param", {}) + for key in data: + if param.get(key, "non_falsy_default") != "non_falsy_default": + data[key] = param[key] + return data + + +@pytest.fixture(scope="session") +def result_objects_factory(): + def _result_objects_factory(profile, requested_status=None): + result_props = [ + # result (status) + [ + script.PASS, + script.FAIL_O, + script.FAIL_UF, + script.MANUAL, + script.POST, + script.NA, + script.ERROR, + ], + # msg + [ + "", + "test msg", + "long test msg " + "x" * 120, # > 120 char + ], + # headers + [ + [], + ["H1", "H2", "H3"], + ], + # data + [ + [], + [["Data1", "Data2", "Data3"], ["Data4", "Data5", "Data6"], ["Loooooong Data7", "Data8", "Data9"]], + ], + # unformatted_headers + [ + [], + ["Unformatted_H1"], + ], + # unformatted_data + [ + [], + [["Data1"], ["Data2"]], + ], + # recommended_action + [ + "", + "This is your recommendation to remediate the issue", + ], + # doc_url + [ + "", + "https://fake_doc_url.local/path1/#section1", + ], + ] + + def _generate_result_obj(result_prop): + return script.Result( + result=result_prop[0], + msg=result_prop[1], + headers=result_prop[2], + data=result_prop[3], + unformatted_headers=result_prop[4], + unformatted_data=result_prop[5], + recommended_action=result_prop[6], + doc_url=result_prop[7], + ) + + def _get_requested_status(requested_status): + if not isinstance(requested_status, list): + requested_status = [requested_status] + all_status = result_props[0] + if not requested_status: + return all_status + return [status for status in all_status if status in requested_status] + + if profile == "fullmesh": + raw_product = product(*result_props) + return [_generate_result_obj(prop) for prop in raw_product] + elif profile == "fail_full": + # All props populated (mainly for FAIL_O, FAIL_UF, MANUAL) + status_list = [script.FAIL_O, script.FAIL_UF, script.MANUAL] + if requested_status: + status_list = _get_requested_status(requested_status) + return [ + _generate_result_obj( + [ + status, # result + result_props[1][1], # msg + result_props[2][1], # headers + result_props[3][1], # data + result_props[4][1], # unformatted_headers + result_props[5][1], # unformatted_data + result_props[6][1], # recommended_action + result_props[7][1], # doc_url + ] + ) + for status in status_list + ] + elif profile == "fail_simple": + # No msg nor unformatted_headers/data (mojority of FAIL_O, FAIL_UF, MANUAL) + status_list = [script.FAIL_O, script.FAIL_UF, script.MANUAL] + if requested_status: + status_list = _get_requested_status(requested_status) + return [ + _generate_result_obj( + [ + status, # result + result_props[1][0], # msg + result_props[2][1], # headers + result_props[3][1], # data + result_props[4][0], # unformatted_headers + result_props[5][0], # unformatted_data + result_props[6][1], # recommended_action + result_props[7][1], # doc_url + ] + ) + for status in status_list + ] + elif profile == "pass": + # Only rec_action and doc (mainly for PASS) + status_list = [script.PASS] + if requested_status: + status_list = _get_requested_status(requested_status) + return [ + _generate_result_obj( + [ + status, # result + result_props[1][0], # msg + result_props[2][0], # headers + result_props[3][0], # data + result_props[4][0], # unformatted_headers + result_props[5][0], # unformatted_data + result_props[6][1], # recommended_action + result_props[7][1], # doc_url + ] + ) + for status in status_list + ] + elif profile == "only_msg": + # Only msg (mainly for PASS, NA, MANUAL, POST, ERROR) + status_list = [script.PASS, script.NA, script.MANUAL, script.POST, script.ERROR] + if requested_status: + status_list = _get_requested_status(requested_status) + return [ + _generate_result_obj( + [ + status, # result + result_props[1][1], # msg + result_props[2][0], # headers + result_props[3][0], # data + result_props[4][0], # unformatted_headers + result_props[5][0], # unformatted_data + result_props[6][0], # recommended_action + result_props[7][0], # doc_url + ] + ) + for status in status_list + ] + elif profile == "only_long_msg": + # Only long msg (mainly for NA and ERROR) + status_list = [script.NA, script.ERROR] + if requested_status: + status_list = _get_requested_status(requested_status) + return [ + _generate_result_obj( + [ + status, # result + result_props[1][2], # msg + result_props[2][0], # headers + result_props[3][0], # data + result_props[4][0], # unformatted_headers + result_props[5][0], # unformatted_data + result_props[6][0], # recommended_action + result_props[7][0], # doc_url + ] + ) + for status in status_list + ] else: - log.error("Command `%s` not found in test data `conn_cmds`", command) - raise Exception("FAILURE IN PYTEST") + raise ValueError("Unexpected profile - {}".format(profile)) + return _result_objects_factory -@pytest.fixture -def mock_conn(monkeypatch, conn_failure, conn_cmds): - MockConnection.conn_failure = conn_failure - MockConnection.conn_cmds = conn_cmds - monkeypatch.setattr(script, "Connection", MockConnection) +@pytest.fixture(scope="session") +def check_factory(): + def _check_factory(check_id, check_title, result_obj): + @script.check_wrapper(check_title=check_title) + def _check(**kwargs): + if result_obj.result == script.ERROR: + raise Exception(result_obj.msg) + else: + return result_obj -@pytest.fixture -def cmd_outputs(): - """ - Mocked output for `run_cmd` function. - This is used to avoid executing real commands in tests. - """ - return { - "ls -aslh /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin": { - "splitlines": False, - "output": "6.1G -rwxr-xr-x 1 root root 6.1G Apr 3 16:36 /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin\napic1#", - } - } + _check.__name__ = check_id # Set the function name for the check + return _check + return _check_factory -@pytest.fixture -def mock_run_cmd(monkeypatch, cmd_outputs): - """ - Mock the `run_cmd` function to avoid executing real commands. - This is useful for tests that do not require actual command execution. - """ - def _mock_run_cmd(cmd, splitlines=False): - details = cmd_outputs.get(cmd) - if details is None: - log.error("Command `%s` not found in test data", cmd) - return "" - if details.get("CalledProcessError"): - raise CalledProcessError(127, cmd) - - splitlines = details.get("splitlines", False) - output = details.get("output") - if output is None: - log.error("Output for cmd `%s` not found in test data", cmd) - output = "" +@pytest.fixture(scope="session") +def check_funcs_factory(check_factory): + def _check_funcs_factory(result_objects): + check_funcs = [] + for idx, result_obj in enumerate(result_objects): + check_id = "fake_{}_check".format(idx) + check_title = "Fake Check {}".format(idx) + check_func = check_factory(check_id, check_title, result_obj) + check_funcs.append(check_func) + return check_funcs - log.debug("Mocked run_cmd called with args: %s, kwargs: %s", cmd, splitlines) - if splitlines: - return output.splitlines() - else: - return output - monkeypatch.setattr(script, "run_cmd", _mock_run_cmd) + return _check_funcs_factory diff --git a/tests/standby_sup_sync_check/test_standby_sup_sync_check.py b/tests/standby_sup_sync_check/test_standby_sup_sync_check.py deleted file mode 100644 index f11a0a42..00000000 --- a/tests/standby_sup_sync_check/test_standby_sup_sync_check.py +++ /dev/null @@ -1,174 +0,0 @@ -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 -eqptSupC_api = 'eqptSupC.json' -eqptSupC_api += '?query-target-filter=eq(eqptSupC.rdSt,"standby")' - -""" -Bug cversion/tversion matrix based on image size - -4.2(7t)+ - fixed versions LT 2 Gigs: 4.2(7t)+ -5.2(5d)+ - fixed versions LT 2 Gigs: 5.2(7f)+ -5.3(1d)+ - fixed versions LT 2 Gigs: 5.3(1d)+ -6.0(1g)+ - fixed versions LT 2 Gigs: 6.0(1g), 6.0(1j). 32-bit only: 6.0(2h), 6.0(2j). 64-bit: NONE -6.1(1f)+ - fixed versions LT 2 Gigs: NONE -""" - -@pytest.mark.parametrize( - "icurl_outputs, cversion, tversion, expected_result", - [ - ## NO TVERSION - MANUAL - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "5.2(1a)", - None, - script.MANUAL, - ), - - ### CVERSION 4.2 - ## cversion 4.2 -nofix, tversion 4.2 -fix LT 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "4.2(7a)", - "4.2(8d)", - script.PASS, - ), - ## cversion 4.2 -nofix, tversion 5.2 -fix but over 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "4.2(7a)", - "5.2(5d)", - script.FAIL_UF, - ), - ## cversion 4.2 -nofix, tversion 5.2 -fix and LT 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "4.2(7a)", - "5.2(7f)", - script.PASS, - ), - ## cversion 4.2 -nofix, tversion 5.3 -fix and LT 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "4.2(7a)", - "5.3(1d)", - script.PASS, - ), - ## cversion 4.2 -nofix, tversion 6.0 -fix and LT 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "4.2(7a)", - "6.0(8d)", - script.FAIL_UF, - ), - ## cversion 4.2 -nofix, tversion 6.1 -fix and LT 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "4.2(7a)", - "6.1(1f)", - script.FAIL_UF, - ), - ## cversion 4.2 -fix, tversion 6.0 -fix but over 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "4.2(7t)", - "6.0(6h)", - script.PASS, - ), - ## cversion 4.2 -fix, tversion 6.1 -fix but over 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "4.2(7t)", - "6.1(1f)", - script.PASS, - ), - - - ### CVERSION 5.2 - ## cversion 5.2 -nofix, tversion 5.2 -fix but over 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "5.2(4a)", - "5.2(7a)", - script.FAIL_UF, - ), - ## cversion 5.2 -nofix, tversion 5.2 -fix LT 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "5.2(4a)", - "5.2(7f)", - script.PASS, - ), - ## cversion 5.2 -nofix, tversion 5.3 -fix LT 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "5.2(4a)", - "5.3(1d)", - script.PASS, - ), - ## cversion 5.2 -nofix, tversion 6.0 -fix but over 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "5.2(4a)", - "6.0(8d)", - script.FAIL_UF, - ), - ## cversion 5.2 -nofix, tversion 6.1 -fix but over 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "5.2(4a)", - "6.1(1f)", - script.FAIL_UF, - ), - ## cversion 5.2 -fix, tversion 6.1 -fix but over 2G - ( - {eqptSupC_api: read_data(dir, "eqptSupC_POS.json"), - }, - "5.2(5d)", - "6.1(1f)", - script.PASS, - ), - - - ## NO STANDBY SUPS - ( - {eqptSupC_api: read_data(dir, "eqptSupC_NEG.json"), - }, - "4.2(7a)", - "6.1(1f)", - script.PASS, - ), - - ], -) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.standby_sup_sync_check( - 1, - 1, - script.AciVersion(cversion), - script.AciVersion(tversion) if tversion else None - ) - assert result == expected_result \ No newline at end of file diff --git a/tests/static_route_overlap_check/test_static_route_overlap_check.py b/tests/static_route_overlap_check/test_static_route_overlap_check.py deleted file mode 100644 index 270641ef..00000000 --- a/tests/static_route_overlap_check/test_static_route_overlap_check.py +++ /dev/null @@ -1,67 +0,0 @@ -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 -staticRoutes = 'ipRouteP.json?query-target-filter=and(wcard(ipRouteP.dn,"/32"))' -staticroute_vrf = 'l3extRsEctx.json' -bds_in_vrf = 'fvRsCtx.json' -subnets_in_bd = 'fvSubnet.json' - - -@pytest.mark.parametrize( - "icurl_outputs, cversion, tversion, expected_result", - [ - ##FAIL = AFFECTED VERSION + AFFECTED MO - ( - {staticRoutes: read_data(dir, "ipRouteP_pos.json"), - staticroute_vrf: read_data(dir, "l3extRsEctx.json"), - bds_in_vrf: read_data(dir, "fvRsCtx.json"), - subnets_in_bd: read_data(dir, "fvSubnet.json")}, - "4.2(7f)", "5.2(4d)", script.FAIL_O, - ), - ##FAIL = AFFECTED VERSION + AFFECTED MO - ( - {staticRoutes: read_data(dir, "ipRouteP_pos.json"), - staticroute_vrf: read_data(dir, "l3extRsEctx.json"), - bds_in_vrf: read_data(dir, "fvRsCtx.json"), - subnets_in_bd: read_data(dir, "fvSubnet.json")}, - "5.1(1a)", "5.2(4d)", script.FAIL_O, - ), - ##PASS = AFFECTED VERSION + NON-AFFECTED MO - ( - {staticRoutes: read_data(dir, "ipRouteP_neg.json"), - staticroute_vrf: read_data(dir, "l3extRsEctx.json"), - bds_in_vrf: read_data(dir, "fvRsCtx.json"), - subnets_in_bd: read_data(dir, "fvSubnet.json")}, - "4.2(7f)", "5.2(4d)", script.PASS, - ), - ## PASS = AFFECTED VERSION + AFFECTED MO NON EXISTING - ( - {staticRoutes: read_data(dir, "ipRouteP_empty.json"), - staticroute_vrf: read_data(dir, "l3extRsEctx.json"), - bds_in_vrf: read_data(dir, "fvRsCtx.json"), - subnets_in_bd: read_data(dir, "fvSubnet.json")}, - "4.2(7f)", "5.2(4d)", script.PASS, - ), - ## PASS = NON-AFFECTED VERSION + AFFECTED MO - ( - {staticRoutes: read_data(dir, "ipRouteP_pos.json"), - staticroute_vrf: read_data(dir, "l3extRsEctx.json"), - bds_in_vrf: read_data(dir, "fvRsCtx.json"), - subnets_in_bd: read_data(dir, "fvSubnet.json")}, - "4.2(7f)", "5.2(6e)", script.PASS, - ), - ], -) -def test_logic(mock_icurl, cversion, tversion, expected_result): - result = script.static_route_overlap_check(1, 1, script.AciVersion(cversion), script.AciVersion(tversion)) - assert result == expected_result diff --git a/tests/subnet_scope_check/test_subnet_scope_check.py b/tests/subnet_scope_check/test_subnet_scope_check.py deleted file mode 100644 index 2b9a53c7..00000000 --- a/tests/subnet_scope_check/test_subnet_scope_check.py +++ /dev/null @@ -1,63 +0,0 @@ -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 -bd_api = 'fvBD.json' -bd_api += '?rsp-subtree=children&rsp-subtree-class=fvSubnet&rsp-subtree-include=required' - -epg_api = 'fvAEPg.json?' -epg_api += 'rsp-subtree=children&rsp-subtree-class=fvSubnet&rsp-subtree-include=required' - -@pytest.mark.parametrize( - "icurl_outputs, cversion, expected_result", - [ - ( - {bd_api: read_data(dir, "fvBD.json"), - epg_api: read_data(dir, "fvAEPg_empty.json"), - "fvRsBd.json": read_data(dir, "fvRsBd.json")}, - "4.2(6a)", - script.NA, - ), - ( - {bd_api: read_data(dir, "fvBD.json"), - epg_api: read_data(dir, "fvAEPg_pos.json"), - "fvRsBd.json": read_data(dir, "fvRsBd.json")}, - "4.2(6a)", - script.FAIL_O, - ), - ( - {bd_api: read_data(dir, "fvBD.json"), - epg_api: read_data(dir, "fvAEPg_pos.json"), - "fvRsBd.json": read_data(dir, "fvRsBd.json")}, - "5.1(1a)", - script.FAIL_O, - ), - ( - {bd_api: read_data(dir, "fvBD.json"), - epg_api: read_data(dir, "fvAEPg_neg.json"), - "fvRsBd.json": read_data(dir, "fvRsBd.json")}, - "5.1(1a)", - script.PASS, - ), - ( - {bd_api: read_data(dir, "fvBD.json"), - epg_api: read_data(dir, "fvAEPg_neg.json"), - "fvRsBd.json": read_data(dir, "fvRsBd.json")}, - "5.2(8h)", - script.PASS, - ), - - ], -) -def test_logic(mock_icurl, cversion, expected_result): - result = script.subnet_scope_check(1, 1, script.AciVersion(cversion)) - assert result == expected_result diff --git a/tests/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py b/tests/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py deleted file mode 100644 index 5b160642..00000000 --- a/tests/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py +++ /dev/null @@ -1,46 +0,0 @@ -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 -partitions = 'eqptcapacityFSPartition.json' -partitions += '?query-target-filter=eq(eqptcapacityFSPartition.path,"/bootflash")' - -download_sts = 'maintUpgJob.json' -download_sts += '?query-target-filter=and(eq(maintUpgJob.dnldStatus,"downloaded")' -download_sts += ',eq(maintUpgJob.desiredVersion,"n9000-16.0(2h)"))' - -@pytest.mark.parametrize( - "icurl_outputs, tversion, expected_result", - [ - ( - {partitions: read_data(dir, "eqptcapacityFSPartition.json"), - download_sts: read_data(dir, "maintUpgJob_not_downloaded.json")}, - "6.0(2h)", - script.FAIL_UF, - ), - ( - {partitions: read_data(dir, "eqptcapacityFSPartition.json"), - download_sts: read_data(dir, "maintUpgJob_pre_downloaded.json")}, - "6.0(2h)", - script.PASS, - ), - ( - {partitions: read_data(dir, "eqptcapacityFSPartition.json"), - download_sts: read_data(dir, "maintUpgJob_old_ver_no_prop.json")}, - "6.0(2h)", - script.FAIL_UF, - ), - ], -) -def test_logic(mock_icurl, tversion, expected_result): - result = script.switch_bootflash_usage_check(1, 1, script.AciVersion(tversion)) - assert result == expected_result diff --git a/tests/test_AciResult.py b/tests/test_AciResult.py index c427712d..b4b9e6cd 100644 --- a/tests/test_AciResult.py +++ b/tests/test_AciResult.py @@ -1,27 +1,29 @@ import pytest import importlib -import json from six import string_types script = importlib.import_module("aci-preupgrade-validation-script") +AciResult = script.AciResult +Result = script.Result @pytest.mark.parametrize( - "func_name, name, description, result, recommended_action, reason, doc_url, column, row, unformatted_column, unformatted_rows, expected_show, expected_criticality, expected_passed", + "func_name, name, result_obj, expected_show, expected_criticality, expected_passed", [ # Check 1: NA ( "fake_func_name_NA_test", "NA", - "", - script.NA, - "", - "", - "", - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], + Result( + result=script.NA, + recommended_action="", + msg="", + doc_url="", + headers=["col1", "col2"], + data=[["row1", "row2"], ["row3", "row4"]], + unformatted_headers=["col1", "col2"], + unformatted_data=[["row1", "row2"], ["row3", "row4"]], + ), False, "informational", "passed" @@ -30,15 +32,16 @@ ( "fake_func_name_PASS_test", "PASS", - "", - script.PASS, - "", - "", - "", - [], - [], - [], - [], + Result( + result=script.PASS, + recommended_action="", + msg="", + doc_url="", + headers=[], + data=[], + unformatted_headers=[], + unformatted_data=[], + ), True, "informational", "passed" @@ -47,15 +50,16 @@ ( "fake_func_name_POST_test", "POST", - "", - script.POST, - "reboot", - "test reason", - "https://test_doc_url.html", - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], + Result( + result=script.POST, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=["col1", "col2"], + data=[["row1", "row2"], ["row3", "row4"]], + unformatted_headers=["col1", "col2"], + unformatted_data=[["row1", "row2"], ["row3", "row4"]], + ), False, "informational", "failed" @@ -64,15 +68,16 @@ ( "fake_func_name_MANUAL_test", "MANUAL", - "", - script.MANUAL, - "reboot", - "test reason", - "https://test_doc_url.html", - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], + Result( + result=script.MANUAL, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=["col1", "col2"], + data=[["row1", "row2"], ["row3", "row4"]], + unformatted_headers=["col1", "col2"], + unformatted_data=[["row1", "row2"], ["row3", "row4"]], + ), True, "warning", "failed" @@ -81,15 +86,16 @@ ( "fake_func_name_ERROR_test", "ERROR", - "", - script.ERROR, - "reboot", - "test reason", - "https://test_doc_url.html", - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], + Result( + result=script.ERROR, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=["col1", "col2"], + data=[["row1", "row2"], ["row3", "row4"]], + unformatted_headers=["col1", "col2"], + unformatted_data=[["row1", "row2"], ["row3", "row4"]], + ), True, "major", "failed" @@ -98,15 +104,16 @@ ( "fake_func_name_FAIL_UF_test", "FAIL_UF", - "", - script.FAIL_UF, - "reboot", - "test reason", - "https://test_doc_url.html", - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], - ["col1", "col2"], - [["row1", "row2"], ["row3", "row4"]], + Result( + result=script.FAIL_UF, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=["col1", "col2"], + data=[["row1", "row2"], ["row3", "row4"]], + unformatted_headers=["col1", "col2"], + unformatted_data=[["row1", "row2"], ["row3", "row4"]], + ), True, "critical", "failed" @@ -115,15 +122,16 @@ ( "fake_func_name_FAIL_O_test", "FAIL_O", - "", - script.FAIL_O, - "reboot", - "test reason", - "https://test_doc_url.html", - ["col1", "col2", "col3"], - [["row1", "row2", "row3"], ["row4", "row5", "row6"]], - ["col4", "col5"], - [["row1", "row2"], ["row3", "row4"]], + Result( + result=script.FAIL_O, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=["col1", "col2", "col3"], + data=[["row1", "row2", "row3"], ["row4", "row5", "row6"]], + unformatted_headers=["col4", "col5"], + unformatted_data=[["row1", "row2"], ["row3", "row4"]], + ), True, "critical", "failed" @@ -132,15 +140,16 @@ ( "fake_func_name_FAIL_O_formatted_only_test", "FAIL_O Formatted only", - "", - script.FAIL_O, - "reboot", - "test reason", - "https://test_doc_url.html", - ["col1", "col2", "col3"], - [["row1", None, 3], ["row4", None, 3]], - [], - [], + Result( + result=script.FAIL_O, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=["col1", "col2", "col3"], + data=[["row1", None, 3], ["row4", None, 3]], + unformatted_headers=[], + unformatted_data=[], + ), True, "critical", "failed" @@ -149,15 +158,16 @@ ( "fake_func_name_FAIL_O_unformatted_only_test", "FAIL_O Unformatted only", - "", - script.FAIL_O, - "reboot", - "test reason", - "https://test_doc_url.html", - [], - [], - ["col1", "col2", "col3"], - [["row1", None, 3], ["row4", None, 3]], + Result( + result=script.FAIL_O, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=[], + data=[], + unformatted_headers=["col1", "col2", "col3"], + unformatted_data=[["row1", None, 3], ["row4", None, 3]], + ), True, "critical", "failed" @@ -167,35 +177,24 @@ def test_AciResult( func_name, name, - description, - result, - recommended_action, - reason, - doc_url, - column, - row, - unformatted_column, - unformatted_rows, + result_obj, expected_show, expected_criticality, expected_passed, ): - synth = script.AciResult(func_name, name, description) - synth.updateWithResults(result, recommended_action, reason, doc_url, column, row, unformatted_column, unformatted_rows) - file = synth.writeResult() - with open(file, "r") as f: - data = json.load(f) - assert data["ruleId"] == func_name - assert data["showValidation"] == expected_show - assert data["severity"] == expected_criticality - assert data["ruleStatus"] == expected_passed - for entry in data["failureDetails"]["data"]: + synth = AciResult(func_name, name, result_obj) + assert synth.ruleId == func_name + assert synth.showValidation == expected_show + assert synth.severity == expected_criticality + assert synth.ruleStatus == expected_passed + for entry in synth.failureDetails["data"]: for vals in entry.values(): assert isinstance(vals, string_types) - for entry in data["failureDetails"]["unformatted_data"]: + for entry in synth.failureDetails["unformatted_data"]: for vals in entry.values(): assert isinstance(vals, string_types) + @pytest.mark.parametrize( "headers, data", [ @@ -206,12 +205,13 @@ def test_AciResult( ) def test_invalid_headers_or_data(headers, data): with pytest.raises(TypeError): - synth = script.AciResult("func_name", "Check Title", "A Description") - synth.craftData( + synth = AciResult("func_name", "Check Title") + synth.convert_data( column=headers, rows=data, ) + @pytest.mark.parametrize( "headers, data", [ @@ -235,8 +235,8 @@ def test_invalid_headers_or_data(headers, data): ) def test_mismatched_lengths(headers, data): with pytest.raises(ValueError): - synth = script.AciResult("func_name", "Check Title", "A Description") - synth.craftData( + synth = AciResult("func_name", "Check Title") + synth.convert_data( column=headers, rows=data, ) diff --git a/tests/test_CheckManager.py b/tests/test_CheckManager.py new file mode 100644 index 00000000..e5c7e794 --- /dev/null +++ b/tests/test_CheckManager.py @@ -0,0 +1,353 @@ +import pytest +import importlib +import logging +import time +import json +import os + +script = importlib.import_module("aci-preupgrade-validation-script") +AciVersion = script.AciVersion +AciResult = script.AciResult +Result = script.Result +CheckManager = script.CheckManager +check_wrapper = script.check_wrapper + + +# ---------------------------- +# Fixtures, Helper Functions +# ---------------------------- +def assert_aci_result_file_with_error(cm, check_id, check_title, msg): + filepath = cm.rm.get_result_filepath(check_id) + with open(filepath, "r") as f: + aci_result = json.load(f) + assert aci_result["ruleId"] == check_id + assert aci_result["name"] == check_title + assert aci_result["ruleStatus"] == AciResult.FAIL + assert aci_result["severity"] == "major" + assert aci_result["reason"] == msg + assert aci_result["failureDetails"]["failType"] == script.ERROR + + +@pytest.fixture +def mock_generate_thread(monkeypatch, request): + """Mock thread in ThreadManager to raise an exception in thread.start(). + + This is to test exception handleing in ThreadManager._start_thread(). + """ + + check_id = request.param.get("check_id") + exception = request.param.get("exception") + + def thread_start_with_exception(timeout=5.0): + raise exception + + def _mock_generate_thread(self, target, args=(), kwargs=None): + if kwargs is None: + kwargs = {} + thread = script.CustomThread(target=target, name=target.__name__, args=args, kwargs=kwargs) + thread.daemon = True + # Mock only a specifieid thread instance. Otherwise, exception is raised in + # the monitoring thread which doesn't use _start_thread(). + if thread.name == check_id: + thread.start = thread_start_with_exception + return thread + + monkeypatch.setattr(script.ThreadManager, "_generate_thread", _mock_generate_thread) + + +# ---------------------------- +# Tests +# ---------------------------- +class TestCheckManager: + @pytest.fixture(scope="class") + def expected_result_objects(self, result_objects_factory): + return result_objects_factory("fullmesh") + + @pytest.fixture(scope="class") + def check_funcs(self, check_funcs_factory, expected_result_objects): + return check_funcs_factory(expected_result_objects) + + @pytest.fixture(scope="class") + def cm(self, check_funcs): + _cm = CheckManager() + _cm.check_funcs = check_funcs + return _cm + + def test_initialize_checks(self, caplog, cm): + caplog.set_level(logging.CRITICAL) # Skip logging as it's too noisy for this + + cm.initialize_checks() + + # With init, only titles should be available + assert cm.get_check_title("fake_0_check") == "Fake Check 0" + assert cm.get_check_title("fake_10_check") == "Fake Check 10" + assert cm.get_check_result("fake_0_check") is None + assert cm.get_check_result("fake_10_check") is None + + # Check number of initialized checks in result files + result_files = os.listdir(script.JSON_DIR) + assert len(result_files) == cm.total_checks + + # Check the filename of result files and their `ruleStatus` + for check_id in cm.check_ids: + filepath = cm.rm.get_result_filepath(check_id) + assert os.path.exists(filepath), "Missing result file: {}".format(filepath) + + with open(filepath, "r") as f: + aci_result = json.load(f) + # At initialize, ruleStatus must be always in-progress + assert aci_result["ruleStatus"] == AciResult.IN_PROGRESS + + def test_run_checks(self, caplog, cm, expected_common_data, expected_result_objects): + caplog.set_level(logging.CRITICAL) # Skip logging as it's too noisy for this + + cm.run_checks(expected_common_data) + + # With run_checks, both titles and result obj should be available + assert cm.get_check_title("fake_0_check") == "Fake Check 0" + assert cm.get_check_title("fake_10_check") == "Fake Check 10" + assert cm.get_check_result("fake_0_check") is expected_result_objects[0] + assert cm.get_check_result("fake_10_check") is expected_result_objects[10] + + # Check the result files + for check_id in cm.check_ids: + filepath = cm.rm.get_result_filepath(check_id) + assert os.path.exists(filepath), "Missing result file: {}".format(filepath) + + with open(filepath, "r") as f: + aci_result = json.load(f) + assert aci_result["ruleId"] == check_id + assert aci_result["name"] == cm.get_check_title(check_id) + assert aci_result["ruleStatus"] in (AciResult.PASS, AciResult.FAIL) + r = cm.get_check_result(check_id) + + # recommended_action: additional note is added with a certain condition + if aci_result["ruleStatus"] == AciResult.FAIL and r.unformatted_headers and r.unformatted_data: + assert aci_result["recommended_action"].startswith(r.recommended_action + "\n Note") + else: + assert aci_result["recommended_action"] == r.recommended_action + + # docUrl + assert aci_result["docUrl"] == r.doc_url + + # failureDetails: matters only when ruleStatus is FAIL + if aci_result["ruleStatus"] == AciResult.FAIL: + assert aci_result["failureDetails"]["failType"] == r.result + try: + data = AciResult.convert_data(r.headers, r.data) + assert aci_result["failureDetails"]["header"] == r.headers + assert aci_result["failureDetails"]["data"] == data + except Exception: + assert aci_result["failureDetails"]["failType"] == script.ERROR + assert aci_result["failureDetails"]["header"] == [] + assert aci_result["failureDetails"]["data"] == [] + + if r.unformatted_headers and r.unformatted_data: + try: + unformatted_data = AciResult.convert_data(r.unformatted_headers, r.unformatted_data) + assert aci_result["failureDetails"]["unformatted_header"] == r.unformatted_headers + assert aci_result["failureDetails"]["unformatted_data"] == unformatted_data + except Exception: + assert aci_result["failureDetails"]["failType"] == script.ERROR + assert aci_result["failureDetails"]["unformatted_header"] == [] + assert aci_result["failureDetails"]["unformatted_data"] == [] + + +@pytest.mark.parametrize( + "api_only, debug_function, expected_total", + [ + (False, None, len(CheckManager.api_checks) + len(CheckManager.ssh_checks) + len(CheckManager.cli_checks)), + (True, None, len(CheckManager.api_checks)), + (False, CheckManager.api_checks[0].__name__, 1), + (True, CheckManager.api_checks[0].__name__, 1), + (False, CheckManager.ssh_checks[0].__name__, 1), + (True, CheckManager.ssh_checks[0].__name__, 0), # api_only for non-api check = 0 + ], +) +def test_total_checks(api_only, debug_function, expected_total): + cm = CheckManager(api_only, debug_function) + assert cm.total_checks == expected_total + + +def test_exception_in_initialize(): + """Exception in initialize is not captured by CheckManager. + The exception should go up to the script's main() and abort the script + because there is likely some fundamental issue in the system""" + cm = CheckManager() + cm.initialize_check = lambda x, y: 1 / 0 # Zero Division Error for a quick exception + with pytest.raises(ZeroDivisionError): + cm.initialize_checks() + + +def test_memerror_in_check(): + @check_wrapper(check_title="Memory Error Check") + def memerr_check(**kwargs): + raise MemoryError + + cm = CheckManager() + cm.check_funcs = [memerr_check] + cm.initialize_checks() + cm.run_checks({"fake_common_data": True}) + + assert_aci_result_file_with_error( + cm, "memerr_check", "Memory Error Check", "Not enough memory to complete this check." + ) + + +def test_exception_in_check(): + @check_wrapper(check_title="Bad Check") + def bad_check(**kwargs): + raise Exception("This is a test exception") + + cm = CheckManager() + cm.check_funcs = [bad_check] + cm.initialize_checks() + cm.run_checks({"fake_common_data": True}) + + assert_aci_result_file_with_error(cm, "bad_check", "Bad Check", "Unexpected Error: This is a test exception") + + +def test_exception_in_finalize_check_due_to_bad_check(): + """Exceptions in `finalize_check` due to bad return value from each check. + + This exception happens in `try` of `check_wrapper`, but `finalize_check` + in the corresponding `except` works as it calls `finalize_check` again + with a valid `Result` object with status ERROR. + """ + + @check_wrapper(check_title="Non-Result Obj Check") + def non_result_check(**kwargs): + return "Not Result Obj" # instead of `Result` obj + + @check_wrapper(check_title="Invalid Result Check") + def invalid_result_check(**kwargs): + # length of header and each row of data must be the same + return Result(result=script.FAIL_O, headers=["H1", "H2"], data=[["D1"], ["D2"]]) + + cm = CheckManager() + cm.check_funcs = [non_result_check, invalid_result_check] + cm.initialize_checks() + cm.run_checks({"fake_common_data": True}) + + checks = [ + { + "id": "non_result_check", + "title": "Non-Result Obj Check", + "msg": "Unexpected Error: The result of non_result_check is not a `Result` object", + }, + { + "id": "invalid_result_check", + "title": "Invalid Result Check", + "msg": "Unexpected Error: Row length (1), data: ['D1'] does not match column length (2).", + }, + ] + for check in checks: + assert_aci_result_file_with_error(cm, check["id"], check["title"], check["msg"]) + + +def test_exception_in_finalize_check(): + """Exception in `finalize_check` itself. + + This could happen when the filesystem is full, permission denied to write + the result file etc. + """ + + # Check exception is caught in try of check_wrapper, then finalize_check + # fails in the corresponding except. + @check_wrapper(check_title="Bad Check With Bad Finalizer") + def bad_check_with_bad_finalizer(**kwargs): + raise Exception("Bad check to test finalize_check failure") + + # Check is good but finalize_check failed in try of check_wrapper, then it + # fails in the corresponding except again. + @check_wrapper(check_title="Good Check With Bad Finalizer") + def good_check_with_bad_finalizer(**kwargs): + return Result(result=script.PASS) + + cm = CheckManager() + cm.finalize_check = lambda x, y: 1 / 0 # Zero Division Error for a quick exception + cm.check_funcs = [bad_check_with_bad_finalizer, good_check_with_bad_finalizer] + cm.initialize_checks() + with pytest.raises(ZeroDivisionError): + cm.run_checks({"fake_common_data": True}) + + +@pytest.mark.parametrize( + "mock_generate_thread", + [ + {"check_id": "good_check_with_thread_start_failure", "exception": RuntimeError("can't start new thread")}, + {"check_id": "good_check_with_thread_start_failure", "exception": RuntimeError("unknown runtime error")}, + {"check_id": "good_check_with_thread_start_failure", "exception": Exception("unknown exception")}, + ], + indirect=True, +) +def test_exception_in_starting_thread(mock_generate_thread): + @check_wrapper(check_title="Good Check With Failure in Starting Thread") + def good_check_with_thread_start_failure(**kwargs): + return Result(result=script.PASS) + + cm = CheckManager() + cm.check_funcs = [good_check_with_thread_start_failure] + cm.initialize_checks() + cm.run_checks({"fake_common_data": True}) + + assert_aci_result_file_with_error( + cm, + "good_check_with_thread_start_failure", + "Good Check With Failure in Starting Thread", + "Skipped due to a failure in starting a thread for this check.", + ) + + +@pytest.mark.parametrize( + "mock_generate_thread", + [ + {"check_id": "good_check_with_start_failure_and_exc_in_callback", "exception": Exception("unknown exception")}, + ], + indirect=True, +) +def test_exception_in_finalize_check_on_thread_failure(mock_generate_thread): + """Exception in failure callback. Should not catch the exception and let the script fail""" + @check_wrapper(check_title="Good Check With Failure in Starting Thread") + def good_check_with_start_failure_and_exc_in_callback(**kwargs): + return Result(result=script.PASS) + + cm = CheckManager() + cm.finalize_check_on_thread_failure = lambda x: 1 / 0 # Zero Division Error for a quick exception + cm.check_funcs = [good_check_with_start_failure_and_exc_in_callback] + cm.initialize_checks() + with pytest.raises(ZeroDivisionError): + cm.run_checks({"fake_common_data": True}) + + +def test_monitor_timeout(): + @check_wrapper(check_title="Timeout Check") + def timeout_check(**kwargs): + time.sleep(60) + + timeout = 1 # sec + cm = CheckManager(timeout=timeout) + cm.check_funcs = [timeout_check] + cm.initialize_checks() + cm.run_checks({"fake_common_data": True}) + assert cm.timeout_event.is_set() + + assert_aci_result_file_with_error( + cm, "timeout_check", "Timeout Check", "Timeout. Unable to finish in time ({} sec).".format(timeout) + ) + + +def test_exception_in_finalize_check_on_thread_timeout(): + """Exception in failure callback. Should not catch the exception and let the script fail""" + @check_wrapper(check_title="Timeout Check") + def timeout_check_with_exc_in_callback(**kwargs): + time.sleep(60) + + timeout = 1 # sec + cm = CheckManager(timeout=timeout) + cm.finalize_check_on_thread_timeout = lambda x: 1 / 0 # Zero Division Error for a quick exception + cm.check_funcs = [timeout_check_with_exc_in_callback] + cm.initialize_checks() + with pytest.raises(ZeroDivisionError): + cm.run_checks({"fake_common_data": True}) + assert cm.timeout_event.is_set() diff --git a/tests/test_ResultManager.py b/tests/test_ResultManager.py new file mode 100644 index 00000000..893b99af --- /dev/null +++ b/tests/test_ResultManager.py @@ -0,0 +1,123 @@ +import importlib +import json + +script = importlib.import_module("aci-preupgrade-validation-script") +AciResult = script.AciResult +Result = script.Result + + +def _test_init_result(rm, fake_checks): + for fake_check in fake_checks: + rm.init_result(**fake_check) + + assert len(rm.titles) == len(fake_checks) + + for check_id in rm.titles: + expected_title = [check["check_title"] for check in fake_checks if check["check_id"] == check_id][0] + assert rm.titles[check_id] == expected_title + + filepath = rm.get_result_filepath(check_id) + with open(filepath, "r") as f: + aci_result = json.load(f) + assert aci_result["ruleId"] == check_id + assert aci_result["name"] == rm.titles[check_id] + assert aci_result["ruleStatus"] == AciResult.IN_PROGRESS + assert aci_result["severity"] == "informational" + assert aci_result["recommended_action"] == "" + assert aci_result["docUrl"] == "" + assert aci_result["failureDetails"]["failType"] == "" + assert aci_result["failureDetails"]["header"] == [] + assert aci_result["failureDetails"]["data"] == [] + assert aci_result["failureDetails"]["unformatted_header"] == [] + assert aci_result["failureDetails"]["unformatted_data"] == [] + + +def _test_update_result(rm, fake_checks): + for fake_check in fake_checks: + rm.update_result(**fake_check) + + inited_fake_checks = [check for check in fake_checks if check["check_id"] in rm.titles] + assert len(rm.results) == len(inited_fake_checks) + + for check_id in rm.results: + expected_result_obj = [check["result_obj"] for check in fake_checks if check["check_id"] == check_id][0] + r = rm.results[check_id] + assert r == expected_result_obj + + filepath = rm.get_result_filepath(check_id) + with open(filepath, "r") as f: + aci_result = json.load(f) + assert aci_result["ruleId"] == check_id + assert aci_result["name"] == rm.titles[check_id] + assert aci_result["ruleStatus"] in (AciResult.PASS, AciResult.FAIL) + if r.unformatted_data: + assert aci_result["recommended_action"].startswith(r.recommended_action) + else: + assert aci_result["recommended_action"] == r.recommended_action + assert aci_result["docUrl"] == r.doc_url + assert aci_result["failureDetails"]["failType"] == "" if r.result == script.PASS else r.result + assert aci_result["failureDetails"]["header"] == r.headers + assert aci_result["failureDetails"]["data"] == AciResult.convert_data(r.headers, r.data) + assert aci_result["failureDetails"]["unformatted_header"] == r.unformatted_headers + assert aci_result["failureDetails"]["unformatted_data"] == AciResult.convert_data(r.unformatted_headers, r.unformatted_data) + + +def test_ResultManager(): + rm = script.ResultManager() + fake_checks_for_init = [ + {"check_id": "puv_1_check", "check_title": "PUV 1"}, + {"check_id": "puv_2_check", "check_title": "PUV 2"}, + ] + fake_checks_for_update = [ + { + "check_id": "puv_1_check", + "result_obj": Result( + result=script.PASS, + recommended_action="", + msg="", + doc_url="", + headers=[], + data=[], + unformatted_headers=[], + unformatted_data=[], + ), + }, + { + "check_id": "puv_2_check", + "result_obj": Result( + result=script.FAIL_UF, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=["col1", "col2"], + data=[["row1", "row2"], ["row3", "row4"]], + unformatted_headers=["col1", "col2"], + unformatted_data=[["row1", "row2"], ["row3", "row4"]], + ), + }, + { + "check_id": "no_init_check", + "result_obj": Result( + result=script.FAIL_UF, + recommended_action="reboot", + msg="test reason", + doc_url="https://test_doc_url.html", + headers=["col1", "col2"], + data=[["row1", "row2"], ["row3", "row4"]], + unformatted_headers=["col1", "col2"], + unformatted_data=[["row1", "row2"], ["row3", "row4"]], + ), + }, + ] + _test_init_result(rm, fake_checks_for_init) + _test_update_result(rm, fake_checks_for_update) + + summary = rm.get_summary() + assert len(summary) == 8 # [PASS, FAIL_O, FAIL_UF, MANUAL, POST, NA, ERROR, 'TOTAL'] + for key in summary: + if key == "TOTAL": + expected_num = len([c for c in fake_checks_for_update if c["check_id"] != "no_init_check"]) + else: + expected_num = len([c for c in fake_checks_for_update if c["result_obj"].result == key and c["check_id"] != "no_init_check"]) + + assert summary[key] == expected_num diff --git a/tests/test_ThreadManager.py b/tests/test_ThreadManager.py new file mode 100644 index 00000000..796e0c69 --- /dev/null +++ b/tests/test_ThreadManager.py @@ -0,0 +1,39 @@ +from __future__ import print_function +import importlib +import time + +script = importlib.import_module("aci-preupgrade-validation-script") + + +def task1(data=""): + time.sleep(2.5) + print("Thread task1: Finishing with data {}".format(data)) + + +def task2(data=""): + time.sleep(0.5) + print("Thread task2: Finishing with data {}".format(data)) + + +def task3(data=""): + time.sleep(0.2) + print("Thread task3: Finishing with data {}".format(data)) + + +def test_ThreadManager(capsys): + tm = script.ThreadManager( + funcs=[task1, task2, task3], + common_kwargs={"data": "common_data"}, + monitor_timeout=1, + callback_on_timeout=lambda x: print("Timeout. Abort {}".format(x)) + ) + tm.start() + tm.join() + + expected_output = """\ +Thread task3: Finishing with data common_data +Thread task2: Finishing with data common_data +Timeout. Abort task1 +""" + captured = capsys.readouterr() + assert captured.out == expected_output diff --git a/tests/test_common_data.py b/tests/test_common_data.py new file mode 100644 index 00000000..e130f506 --- /dev/null +++ b/tests/test_common_data.py @@ -0,0 +1,250 @@ +import pytest +import importlib +import logging +import json + +script = importlib.import_module("aci-preupgrade-validation-script") +AciVersion = script.AciVersion + + +# ------------------------------ +# Data and fixtures +# ------------------------------ + + +@pytest.fixture(autouse=True) +def mock_get_credentials(monkeypatch): + """Mock the get_credentials function to return a fixed username and password.""" + + def _mock_get_credentials(): + return ("admin", "mypassword") + + monkeypatch.setattr(script, "get_credentials", _mock_get_credentials) + + +@pytest.fixture(autouse=True) +def mock_get_target_version(monkeypatch): + """ + Mock `get_target_version()` to return a fixed target version. + Used when the script is run without the `-t` option which is simulated by + `arg_tversion`. + Not using `mock_icurl` because this function involves a user interaction to + select a version. + """ + + def _mock_get_target_version(arg_tversion): + if arg_tversion: + try: + return AciVersion(arg_tversion) + except ValueError as e: + script.prints(e) + raise SystemExit(1) + return AciVersion("6.2(1a)") + + monkeypatch.setattr(script, "get_target_version", _mock_get_target_version) + + +outputs = { + "cversion": [ + { + "firmwareCtrlrRunning": { + "attributes": { + "dn": "topology/pod-1/node-1/sys/ctrlrfwstatuscont/ctrlrrunning", + "version": "6.1(1a)", + } + } + } + ], + "switch_version": [ + {"firmwareRunning": {"attributes": {"peVer": "6.1(1a)", "version": "n9000-16.1(1a)"}}}, + {"firmwareRunning": {"attributes": {"peVer": "6.0(9d)", "version": "n9000-16.0(9d)"}}}, + ], + "vpc_nodes": [ + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-102/nodepep-101", "id": "101"}}}, + {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-102/nodepep-102", "id": "102"}}}, + ], +} + +output_error = [{"error": {"attributes": {"code": "400", "text": "Request failed, unresolved class for dummyClass"}}}] + + +@pytest.fixture(scope="function") +def icurl_outputs(request): + param = getattr(request, "param", {}) + data = { + "firmwareCtrlrRunning.json": outputs["cversion"], + "firmwareRunning.json": outputs["switch_version"], + "fabricNodePEp.json": outputs["vpc_nodes"], + } + for key in data: + if param.get(key, "non_falsy_default") != "non_falsy_default": + data[key] = param[key] + return data + + +@pytest.fixture(scope="function") +def fake_args(request): + data = { + "api_only": False, + "cversion": None, + "tversion": None, + } + for key in data: + if request.param.get(key, "non_falsy_default") != "non_falsy_default": + data[key] = request.param[key] + return data + + +# ------------------------------ +# Tests +# ------------------------------ + + +@pytest.mark.parametrize( + "fake_args, expected_common_data", + [ + # Default, no argparse arguments + pytest.param( + {}, + {}, + id="default_no_args", + ), + # `api_only` is True. + # No `get_credentials()`, no username nor password + pytest.param( + { + "api_only": True, + }, + {"username": None, "password": None}, + id="api_only", + ), + # `arg_tversion` is provided (i.e. -t 6.1(4a)) + pytest.param( + { + "tversion": "6.1(4a)", + }, + { + "tversion": AciVersion("6.1(4a)"), + }, + id="tversion", + ), + # `arg_tversion` and `arg_cversion` are both provided (i.e. -t 6.1(4a)) + pytest.param( + { + "cversion": "6.0(8d)", + "tversion": "6.1(4a)", + }, + { + "cversion": AciVersion("6.0(8d)"), + "tversion": AciVersion("6.1(4a)"), + }, + id="cversion_tversion", + ), + # versions are switch syntax + pytest.param( + { + "cversion": "16.0(4d)", + "tversion": "16.1(4a)", + }, + { + "cversion": AciVersion("6.0(4d)"), + "tversion": AciVersion("6.1(4a)"), + }, + id="cversion_tversion_with_switch_version_syntax", + ), + # versions are APIC image name syntax + pytest.param( + { + "cversion": "aci-apic-dk9.6.0.1a.bin", + "tversion": "aci-apic-dk9.6.2.1a.bin", + }, + { + "cversion": AciVersion("6.0(1a)"), + "tversion": AciVersion("6.2(1a)"), + }, + id="cversion_tversion_with_apic_image_name_syntax", + ), + # versions are Switch image name syntax + pytest.param( + { + "cversion": "n9000-16.0(1a).bin", + "tversion": "n9000-16.2(1a).bin", + }, + { + "cversion": AciVersion("6.0(1a)"), + "tversion": AciVersion("6.2(1a)"), + }, + id="cversion_tversion_with_switch_image_name_syntax", + ), + ], + indirect=["fake_args", "expected_common_data"], +) +def test_common_data(mock_icurl, icurl_outputs, fake_args, expected_common_data): + """test query_common_data and write_script_metadata""" + # --- test for `query_common_data()` + common_data = script.query_common_data( + api_only=fake_args["api_only"], arg_cversion=fake_args["cversion"], arg_tversion=fake_args["tversion"] + ) + for key in common_data: + if isinstance(common_data[key], AciVersion): + assert str(common_data[key]) == str(expected_common_data[key]) + else: + assert common_data[key] == expected_common_data[key] + + # --- test for `write_script_metadata()` + script.write_script_metadata( + api_only=fake_args["api_only"], timeout=1200, total_checks=100, common_data=expected_common_data + ) + with open(script.META_FILE, "r") as f: + meta = json.load(f) + assert meta["name"] == "PreupgradeCheck" + assert meta["method"] == "standalone script" + assert meta["datetime"] == script.ts + script.tz + assert meta["script_version"] == script.SCRIPT_VERSION + assert meta["cversion"] == str(expected_common_data["cversion"]) + assert meta["tversion"] == str(expected_common_data["tversion"]) + assert meta["sw_cversion"] == str(expected_common_data["sw_cversion"]) + assert meta["api_only"] == fake_args["api_only"] + assert meta["timeout"] == 1200 + assert meta["total_checks"] == 100 + + +def test_tversion_invald(): + with pytest.raises(SystemExit): + with pytest.raises(ValueError): + script.query_common_data(arg_cversion="6.0(1a)", arg_tversion="invalid_version") + + +def test_cversion_invald(): + with pytest.raises(SystemExit): + with pytest.raises(ValueError): + script.query_common_data(arg_cversion="invalid_version", arg_tversion="6.0(1a)") + + +@pytest.mark.parametrize( + "icurl_outputs, print_output", + [ + # `get_cversion()` failure + ({"firmwareCtrlrRunning.json": output_error}, "Checking current APIC version..."), + # `get_switch_version()` failure + ({"firmwareRunning.json": output_error}, "Gathering Lowest Switch Version from Firmware Repository..."), + # `get_vpc_nodes()` failure + ({"fabricNodePEp.json": output_error}, "Collecting VPC Node IDs..."), + ], + indirect=["icurl_outputs"], +) +def test_icurl_failure_in_query_common_data(capsys, caplog, mock_icurl, print_output): + caplog.set_level(logging.CRITICAL) + with pytest.raises(SystemExit): + with pytest.raises(Exception): + script.query_common_data() + captured = capsys.readouterr() + expected_output = ( + print_output + + """ + +Error: Your current ACI version does not have requested class +Initial query failed. Ensure APICs are healthy. Ending script run. +""" + ) + assert captured.out.endswith(expected_output), "captured.out is:\n{}".format(captured.out) diff --git a/tests/test_main.py b/tests/test_main.py index 41c4543f..00f5413b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,22 +1,158 @@ import pytest import importlib +import os +import json script = importlib.import_module("aci-preupgrade-validation-script") -AciVersion = script.AciVersion +AciResult = script.AciResult +CheckManager = script.CheckManager +# ---------------------------- +# Fixtures +# ---------------------------- +@pytest.fixture +def mock_query_common_data(monkeypatch, expected_common_data): + def _mock_query_common_data(api_only, args_cversion, args_tversion): + return expected_common_data + + monkeypatch.setattr(script, "query_common_data", _mock_query_common_data) + + +@pytest.fixture +def expected_result_objects(result_objects_factory): + return ( + result_objects_factory("pass") + + result_objects_factory("fail_full", script.FAIL_O) + + result_objects_factory("fail_simple", script.FAIL_UF) + + result_objects_factory("only_msg") + + result_objects_factory("pass") + + result_objects_factory("only_long_msg") + ) + + +@pytest.fixture +def mock_CheckManager_get_check_funcs(monkeypatch, check_funcs_factory, expected_result_objects): + check_funcs = check_funcs_factory(expected_result_objects) + + def _mock_CheckManager_get_check_funcs(self): + return check_funcs + + monkeypatch.setattr(script.CheckManager, "get_check_funcs", _mock_CheckManager_get_check_funcs) + + +# ---------------------------- +# Tests +# ---------------------------- def test_args_version(capsys): script.main(["--version"]) captured = capsys.readouterr() - print(captured.out) - assert "{}\n".format(script.SCRIPT_VERSION) == captured.out + assert "{}\n".format(script.SCRIPT_VERSION) == captured.out, "captured.out is =\n{}".format(captured.out) @pytest.mark.parametrize("api_only", [False, True]) def test_args_total_checks(capsys, api_only): args = ["--total-checks", "--api-only"] if api_only else ["--total-checks"] - checks = script.get_checks(api_only=api_only, debug_function=None) + + cm = CheckManager(api_only) + expected_output = "Total Number of Checks: {}\n".format(cm.total_checks) + script.main(args) captured = capsys.readouterr() - print(captured.out) - assert "Total Number of Checks: {}\n".format(len(checks)) == captured.out + assert captured.out == expected_output, "captured.out is =\n{}".format(captured.out) + + +def test_main(capsys, mock_query_common_data, mock_CheckManager_get_check_funcs, expected_result_objects): + script.main(["--no-cleanup"]) + + for idx, result_obj in enumerate(expected_result_objects): + check_id = "fake_{}_check".format(idx) + check_title = "Fake Check {}".format(idx) + expected_aci_result_obj = AciResult(check_id, check_title, result_obj) + expected_aci_result = expected_aci_result_obj.as_dict() + # Err msg from try/except in `check_wrapper()` + if result_obj.result == script.ERROR: + expected_aci_result["reason"] = "Unexpected Error: {}".format(expected_aci_result["reason"]) + + with open(os.path.join(script.JSON_DIR, check_id + ".json")) as f: + aci_result = json.load(f) + assert aci_result == expected_aci_result + + captured = capsys.readouterr() + assert captured.out.startswith( + """\ + ==== {ts}{tz}, Script Version {version} ==== + +!!!! Check https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script for Latest Release !!!! + +Progress:""".format( + ts=script.ts, + tz=script.tz, + version=script.SCRIPT_VERSION, + ) + ), "captured.out is =\n{}".format(captured.out) + + assert captured.out.endswith( + """\ +11/11 checks completed\r + + +=== Check Result (failed only) === + +[Check 2/11] Fake Check 1... test msg FAIL - OUTAGE WARNING!! + H1 H2 H3 + -- -- -- + Data1 Data2 Data3 + Data4 Data5 Data6 + Loooooong Data7 Data8 Data9 + + Unformatted_H1 + -------------- + Data1 + Data2 + + Recommended Action: This is your recommendation to remediate the issue + Reference Document: https://fake_doc_url.local/path1/#section1 + + +[Check 3/11] Fake Check 2... FAIL - UPGRADE FAILURE!! + H1 H2 H3 + -- -- -- + Data1 Data2 Data3 + Data4 Data5 Data6 + Loooooong Data7 Data8 Data9 + + Recommended Action: This is your recommendation to remediate the issue + Reference Document: https://fake_doc_url.local/path1/#section1 + + +[Check 6/11] Fake Check 5... test msg MANUAL CHECK REQUIRED +[Check 7/11] Fake Check 6... test msg POST UPGRADE CHECK REQUIRED +[Check 8/11] Fake Check 7... Unexpected Error: test msg ERROR !! +[Check 11/11] Fake Check 10... Unexpected Error: long test msg xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ERROR !! + +=== Summary Result === + +PASS : 3 +FAIL - OUTAGE WARNING!! : 1 +FAIL - UPGRADE FAILURE!! : 1 +MANUAL CHECK REQUIRED : 1 +POST UPGRADE CHECK REQUIRED : 1 +N/A : 2 +ERROR !! : 2 +TOTAL : 11 + + Pre-Upgrade Check Complete. + Next Steps: Address all checks flagged as FAIL, ERROR or MANUAL CHECK REQUIRED + + Result output and debug info saved to below bundle for later reference. + Attach this bundle to Cisco TAC SRs opened to address the flagged checks. + + Result Bundle: {bundle_loc} + +==== Script Version {version} FIN ==== +""".format( + version=script.SCRIPT_VERSION, + bundle_loc="/".join([os.getcwd(), script.BUNDLE_NAME]), + ) + ), "captured.out is =\n{}".format(captured.out) diff --git a/tests/test_prepare.py b/tests/test_prepare.py deleted file mode 100644 index 5906bb4c..00000000 --- a/tests/test_prepare.py +++ /dev/null @@ -1,327 +0,0 @@ -import pytest -import importlib -import logging -import json -import os - -script = importlib.import_module("aci-preupgrade-validation-script") -AciVersion = script.AciVersion -AciResult = script.AciResult - - -@pytest.fixture(autouse=True) -def mock_get_credentials(monkeypatch): - """Mock the get_credentials function to return a fixed username and password.""" - - def _mock_get_credentials(): - return ("admin", "mypassword") - - monkeypatch.setattr(script, "get_credentials", _mock_get_credentials) - - -@pytest.fixture(autouse=True) -def mock_get_target_version(monkeypatch): - """ - Mock `get_target_version()` to return a fixed target version. - Used when the script is run without the `-t` option which is simulated by - `arg_tversion`. - Not using `mock_icurl` because this function involves a user interaction to - select a version. - """ - - def _mock_get_target_version(arg_tversion): - if arg_tversion: - try: - return AciVersion(arg_tversion) - except ValueError as e: - script.prints(e) - raise SystemExit(1) - return AciVersion("6.2(1a)") - - monkeypatch.setattr(script, "get_target_version", _mock_get_target_version) - - -outputs = { - "cversion": [ - { - "firmwareCtrlrRunning": { - "attributes": { - "dn": "topology/pod-1/node-1/sys/ctrlrfwstatuscont/ctrlrrunning", - "version": "6.1(1a)", - } - } - } - ], - "switch_version": [ - {"firmwareRunning": {"attributes": {"peVer": "6.1(1a)", "version": "n9000-16.1(1a)"}}}, - {"firmwareRunning": {"attributes": {"peVer": "6.0(9d)", "version": "n9000-16.0(9d)"}}}, - ], - "vpc_nodes": [ - {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-102/nodepep-101", "id": "101"}}}, - {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-102/nodepep-102", "id": "102"}}}, - ], -} - - -@pytest.mark.parametrize( - "icurl_outputs, api_only, arg_tversion, arg_cversion, debug_function, expected_result", - [ - # Default, no argparse arguments - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - False, - None, - None, - None, - {"username": "admin", "password": "mypassword", "cversion": AciVersion("6.1(1a)"), "tversion": AciVersion("6.2(1a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]}, - ), - # `api_only` is True (i.e. --puv) - # No `get_credentials()`, no username nor password - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - True, - None, - None, - None, - {"username": None, "password": None, "cversion": AciVersion("6.1(1a)"), "tversion": AciVersion("6.2(1a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]}, - ), - # `arg_tversion` is provided (i.e. -t 6.1(4a)) - # The version `get_target_version()` is ignored. - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - False, - "6.1(4a)", - None, - None, - {"username": "admin", "password": "mypassword", "cversion": AciVersion("6.1(1a)"), "tversion": AciVersion("6.1(4a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]}, - ), - # `arg_tversion` and `arg_cversion` are both provided (i.e. -t 6.1(4a)) - # The version `get_target_version()` is ignored. - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - False, - "6.1(4a)", - "6.0(8d)", - None, - {"username": "admin", "password": "mypassword", "cversion": AciVersion("6.0(8d)"), "tversion": AciVersion("6.1(4a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]}, - ), - # `arg_tversion`, `arg_cversion` and 'debug_function' are all provided - # The version `get_target_version()` is ignored. - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - False, - "6.1(4a)", - "6.0(4d)", - "ave_eol_check", - {"username": "admin", "password": "mypassword", "cversion": AciVersion("6.0(4d)"), "tversion": AciVersion("6.1(4a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]}, - ), - # versions are switch syntax - # The version `get_target_version()` is ignored. - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - False, - "16.1(4a)", - "16.0(4d)", - "ave_eol_check", - {"username": "admin", "password": "mypassword", "cversion": AciVersion("6.0(4d)"), "tversion": AciVersion("6.1(4a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]}, - ), - # versions are switch or APIC syntax - # The version `get_target_version()` is ignored. - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - False, - "n9000-16.2(1a).bin", - "aci-apic-dk9.6.0.1a.bin", - "ave_eol_check", - {"username": "admin", "password": "mypassword", "cversion": AciVersion("6.0(1a)"), "tversion": AciVersion("6.2(1a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]}, - ), - ], -) -def test_prepare(mock_icurl, api_only, arg_tversion, arg_cversion, debug_function, expected_result): - script.initialize() - checks = script.get_checks(api_only, debug_function) - inputs = script.prepare(api_only, arg_tversion, arg_cversion, checks) - for key, value in expected_result.items(): - if "version" in key: # cversion or tversion - assert isinstance(inputs[key], AciVersion) - assert str(inputs[key]) == str(value) - else: - assert inputs[key] == value - - result_files = os.listdir(script.JSON_DIR) - # Result files should be created for all checks - assert len(result_files) == len(checks) - for check in checks: - # Rule name is known only through the wrapper `check_wrapper`. - # Rule name content should be checked via another unit test. - # Use AciResult class here just to get the filename from `check.__name__`. - ar = AciResult(check.__name__, "unknown_name", "") - file_path = os.path.join(script.JSON_DIR, ar.filename) - assert os.path.exists(file_path), "Missing result file: {}".format(file_path) - with open(file_path, "r") as f: - result = json.load(f) - assert result["ruleId"] == check.__name__ - assert result["ruleStatus"] == AciResult.IN_PROGRESS - - with open(script.META_FILE, "r") as f: - meta = json.load(f) - assert meta["name"] == "PreupgradeCheck" - assert meta["method"] == "standalone script" - assert meta.get("datetime") is not None - assert meta["script_version"] == script.SCRIPT_VERSION - assert meta["cversion"] == str(expected_result["cversion"]) - assert meta["tversion"] == str(expected_result["tversion"]) - assert meta["sw_cversion"] == str(expected_result["sw_cversion"]) - assert meta["api_only"] == api_only - assert meta["total_checks"] == len(checks) - if debug_function: - assert meta["total_checks"] == 1 - - -def test_tversion_invald(): - with pytest.raises(SystemExit): - with pytest.raises(ValueError): - script.prepare(False, "invalid_version", "6.0(1a)", []) - - -def test_cversion_invald(): - with pytest.raises(SystemExit): - with pytest.raises(ValueError): - script.prepare(False, "6.0(1a)", "invalid_version", []) - - -@pytest.mark.parametrize( - "icurl_outputs, api_only, arg_tversion, arg_cversion, debug_function, expected_result", - [ - # `get_cversion()` failure - ( - { - "firmwareCtrlrRunning.json": [{"error": {"attributes": {"code": "400", "text": "Request failed, unresolved class for firmwareCtrlrRunning_fake"}}}], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - False, - None, - None, - None, - """\ -Checking current APIC version... - -Error: Your current ACI version does not have requested class -Initial query failed. Ensure APICs are healthy. Ending script run. -""", - ), - # `get_switch_version()` failure - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": [{"error": {"attributes": {"code": "400", "text": "Request failed, unresolved class for firmwareRunning_fake"}}}], - "fabricNodePEp.json": outputs["vpc_nodes"], - }, - False, - None, - None, - None, - """\ -Gathering Lowest Switch Version from Firmware Repository... - -Error: Your current ACI version does not have requested class -Initial query failed. Ensure APICs are healthy. Ending script run. -""", - ), - # `get_vpc_nodes()` failure - ( - { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": [{"error": {"attributes": {"code": "400", "text": "Request failed, unresolved class for fabricNodePEp_fake"}}}], - }, - False, - None, - None, - None, - """\ -Collecting VPC Node IDs... - -Error: Your current ACI version does not have requested class -Initial query failed. Ensure APICs are healthy. Ending script run. -""", - ), - ], -) -def test_prepare_exception(capsys, caplog, mock_icurl, api_only, arg_tversion, arg_cversion, debug_function, expected_result): - caplog.set_level(logging.CRITICAL) - with pytest.raises(SystemExit): - with pytest.raises(Exception): - checks = script.get_checks(api_only, debug_function) - script.prepare(api_only, arg_tversion, arg_cversion, checks) - captured = capsys.readouterr() - print(captured.out) - assert captured.out.endswith(expected_result) - - -# Unit test focusing only on the result file creation -def test_prepare_initial_result_files(mock_icurl, icurl_outputs): - # Provide required API outputs used inside prepare() - icurl_outputs.update({ - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - }) - - # Create two simple checks with known titles - @script.check_wrapper(check_title="Prepare Check A") - def prep_check_a(**kwargs): - return script.Result(result=script.PASS) - - @script.check_wrapper(check_title="Prepare Check B") - def prep_check_b(**kwargs): - return script.Result(result=script.PASS) - - checks = [prep_check_a, prep_check_b] - - # Run prepare which should only initialize result files - script.prepare(api_only=False, arg_tversion=None, arg_cversion=None, checks=checks) - - # Verify result files and contents - expected = { - "prep_check_a": "Prepare Check A", - "prep_check_b": "Prepare Check B", - } - for func_name, title in expected.items(): - ar = AciResult(func_name, title, "") - file_path = os.path.join(script.JSON_DIR, ar.filename) - assert os.path.exists(file_path), "Missing result file: {}".format(file_path) - with open(file_path, "r") as f: - data = json.load(f) - assert data["ruleId"] == func_name - assert data["name"] == title - assert data["ruleStatus"] == AciResult.IN_PROGRESS diff --git a/tests/test_run_checks.py b/tests/test_run_checks.py deleted file mode 100644 index c8a440e2..00000000 --- a/tests/test_run_checks.py +++ /dev/null @@ -1,188 +0,0 @@ -import importlib -import logging -import json -import os - -script = importlib.import_module("aci-preupgrade-validation-script") -AciVersion = script.AciVersion -JSON_DIR = script.JSON_DIR -AciResult = script.AciResult -Result = script.Result -check_wrapper = script.check_wrapper - - -# 120 = length of `<title> + <msg> --padding-- <RESULT>` in `[Check XX/YY] <title>... <msg> --padding-- <RESULT>` -ERROR_REASON = "This is a test exception to result in `script.ERROR`." -ERROR_REASON_LONG = "This is a looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong test exception to result in `script.ERROR`." # > 120 char - - -def check_builder(func_name, title, result, err_msg, others): - @check_wrapper(check_title=title) - def _check(**kwargs): - _check.__name__ = func_name # Set the function name for the check - if result == script.ERROR: - raise Exception(err_msg) - else: - return Result(result=result, **others) - return _check - - -fake_data_full = { - "msg": "test msg", - "headers": ["H1", "H2", "H3"], - "data": [["Data1", "Data2", "Data3"], ["Data4", "Data5", "Data6"], ["Loooooong Data7", "Data8", "Data9"]], - "unformatted_headers": ["Unformatted_H1"], - "unformatted_data": [["Data1"], ["Data2"]], - "recommended_action": "This is your recommendation to remediate the issue", - "doc_url": "https://fake_doc_url.local/path1/#section1", -} - -fake_data_no_msg_no_unform = { - "headers": ["H1", "H2", "H3"], - "data": [["Data1", "Data2", "Data3"], ["Data4", "Data5", "Data6"], ["Loooooong Data7", "Data8", "Data9"]], - "recommended_action": "This is your recommendation to remediate the issue", - "doc_url": "https://fake_doc_url.local/path1/#section1", -} - -fake_data_error = { - "msg": "Error msg. This should not be printed", -} - -fake_data_only_msg = { - "msg": "test msg", -} - -fake_data_only_long_msg = { - "msg": "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong test msg", # > 120 char -} - -fake_checks_meta = [ - ("fake_check1", "Test Check 1", script.PASS, "", {}), - ("fake_check2", "Test Check 2", script.FAIL_O, "", fake_data_full), - ("fake_check3", "Test Check 3", script.FAIL_UF, "", fake_data_no_msg_no_unform), - ("fake_check4", "Test Check 4", script.MANUAL, "", fake_data_only_msg), - ("fake_check5", "Test Check 5", script.POST, "", fake_data_only_msg), - ("fake_check6", "Test Check 6", script.NA, "", fake_data_only_msg), - ("fake_check7", "Test Check 7", script.ERROR, ERROR_REASON, fake_data_error), - ("fake_check8", "Test Check 8", script.PASS, "", fake_data_only_msg), - ("fake_check9", "Test Check 9", script.ERROR, ERROR_REASON_LONG, fake_data_error), - ("fake_check10", "Test Check 10", script.NA, "", fake_data_only_long_msg), -] - -fake_checks = [ - check_builder(func_name, title, result, err_msg, others) - for func_name, title, result, err_msg, others in fake_checks_meta -] - -fake_result_filenames = [ - "{}.json".format(func_name) for func_name, _, _, _, _ in fake_checks_meta -] - -fake_inputs = { - "username": "admin", - "password": "mypassword", - "cversion": AciVersion("6.1(1a)"), - "tversion": AciVersion("6.2(1a)"), - "sw_cversion": AciVersion("6.1(1a)"), - "vpc_node_ids": ["101", "102"], -} - - -def test_run_checks(capsys, caplog): - caplog.set_level(logging.CRITICAL) # Skip logging.exceptions in pytest output as it is expected. - script.run_checks(fake_checks, fake_inputs) - captured = capsys.readouterr() - print(captured.out) - assert ( - captured.out - == """\ -[Check 1/10] Test Check 1... PASS -[Check 2/10] Test Check 2... test msg FAIL - OUTAGE WARNING!! - H1 H2 H3 - -- -- -- - Data1 Data2 Data3 - Data4 Data5 Data6 - Loooooong Data7 Data8 Data9 - - Unformatted_H1 - -------------- - Data1 - Data2 - - Recommended Action: This is your recommendation to remediate the issue - Reference Document: https://fake_doc_url.local/path1/#section1 - - -[Check 3/10] Test Check 3... FAIL - UPGRADE FAILURE!! - H1 H2 H3 - -- -- -- - Data1 Data2 Data3 - Data4 Data5 Data6 - Loooooong Data7 Data8 Data9 - - Recommended Action: This is your recommendation to remediate the issue - Reference Document: https://fake_doc_url.local/path1/#section1 - - -[Check 4/10] Test Check 4... test msg MANUAL CHECK REQUIRED -[Check 5/10] Test Check 5... test msg POST UPGRADE CHECK REQUIRED -[Check 6/10] Test Check 6... test msg N/A -[Check 7/10] Test Check 7... Unexpected Error: This is a test exception to result in `script.ERROR`. ERROR !! -[Check 8/10] Test Check 8... test msg PASS -[Check 9/10] Test Check 9... Unexpected Error: This is a looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong test exception to result in `script.ERROR`. ERROR !! -[Check 10/10] Test Check 10... looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong test msg N/A - -=== Summary Result === - -PASS : 2 -FAIL - OUTAGE WARNING!! : 1 -FAIL - UPGRADE FAILURE!! : 1 -MANUAL CHECK REQUIRED : 1 -POST UPGRADE CHECK REQUIRED : 1 -N/A : 2 -ERROR !! : 2 -TOTAL : 10 -""" # noqa: W291 - ) - - json_files = [f for f in os.listdir(JSON_DIR) if f in fake_result_filenames] - assert json_files, "Result JSON file not created" - - for json_file in json_files: - with open(os.path.join(JSON_DIR, json_file)) as f: - data = json.load(f) - - for func_name, title, result, err_msg, others, in fake_checks_meta: - if data["ruleId"] == func_name: - assert data["name"] == title - # reason - if result == script.ERROR: - assert data["reason"].endswith(err_msg) - elif result not in [script.PASS, script.NA]: - msg = others.get("msg", "See Failure Details") - if others.get("unformatted_data"): - msg += ( - "\n" - "Parse failure occurred, the provided data may not be complete. " - "Please contact Cisco TAC to identify the missing data." - ) - assert data["reason"] == msg - else: - assert data["reason"] == others.get("msg", "") - # failureDetails.failType - if result not in [script.PASS, script.NA]: - assert data["failureDetails"]["failType"] == result - else: - assert data["failureDetails"]["failType"] == "" - # failureDetails.data - assert data["failureDetails"]["data"] == AciResult.craftData( - others.get("headers", []), others.get("data", []) - ) - assert data["failureDetails"]["unformatted_data"] == AciResult.craftData( - others.get("unformatted_headers", []), others.get("unformatted_data", []) - ) - # other fields - assert data["recommended_action"] == others.get("recommended_action", "") - assert data["docUrl"] == others.get("doc_url", "") - assert data["description"] == "" - assert data["sub_reason"] == "" From 21877cfab0a97a8f8f5a9557d53def846cae9627 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Mon, 3 Nov 2025 23:42:15 -0800 Subject: [PATCH 02/16] fix: switch_ssd_check regex and add pytest --- aci-preupgrade-validation-script.py | 2 +- tests/checks/switch_ssd_check/faultInst.json | 28 ++++++++++ .../switch_ssd_check/test_switch_ssd_check.py | 54 +++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/checks/switch_ssd_check/faultInst.json create mode 100644 tests/checks/switch_ssd_check/test_switch_ssd_check.py diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 923387d8..583a165e 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -2545,7 +2545,7 @@ def switch_ssd_check(**kwargs): } doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#switch-ssd-health" - cs_regex = r'model \(New: (?P<model>\w+)\),' + cs_regex = r"model:(?P<model>\w+)," faultInsts = icurl('class', 'faultInst.json?query-target-filter=or(eq(faultInst.code,"F3073"),eq(faultInst.code,"F3074"))') for faultInst in faultInsts: diff --git a/tests/checks/switch_ssd_check/faultInst.json b/tests/checks/switch_ssd_check/faultInst.json new file mode 100644 index 00000000..4d3166b7 --- /dev/null +++ b/tests/checks/switch_ssd_check/faultInst.json @@ -0,0 +1,28 @@ +[ + { + "faultInst": { + "attributes": { + "cause": "equipment-flash-worn-out", + "changeSet": "acc:read-write, cap:244198, deltape:0, descr:flash, gbb:0, id:1, lba:0, lifetime:155, majorAlarm:yes, mfgTm:2025-11-03T04:22:06.834+00:00, minorAlarm:no, model:Micron_M550_MTFDDAT256MAY, operSt:ok, peCycles:4285, readErr:39, rev:MU03, ser:14270C7D9F13, tbw:336.453735, type:flash, vendor:Micron, warning:no, wlc:0", + "code": "F3073", + "descr": "SSD has reached 90% lifetime endurance limit. Please replace Switch/Supervisor with ID 0 as soon as possible", + "dn": "topology/pod-1/node-205/sys/ch/supslot-1/sup/flash/fault-F3073", + "rule": "eqpt-flash-flash-worn-out", + "subject": "flash-worn-out" + } + } + }, + { + "faultInst": { + "attributes": { + "cause": "equipment-flash-warning", + "changeSet": "acc:read-write, cap:61057, deltape:23, descr:flash, gbb:0, id:1, lba:0, lifetime:85, majorAlarm:no, mfgTm:2020-09-22T02:21:45.675+00:00, minorAlarm:yes, model:Micron_M600_MTFDDAT064MBF, operSt:ok, peCycles:4290, readErr:0, rev:MC04, ser:MSA20400892, tbw:21.279228, type:flash, vendor:Micron, warning:yes, wlc:0", + "code": "F3074", + "descr": "SSD has reached 80% lifetime and is nearing its endurance limit. Please plan for Switch/Supervisor replacement soon", + "dn": "topology/pod-1/node-101/sys/ch/supslot-1/sup/flash/fault-F3074", + "rule": "eqpt-flash-flash-minor-alarm", + "subject": "flash-minor-alarm" + } + } + } +] diff --git a/tests/checks/switch_ssd_check/test_switch_ssd_check.py b/tests/checks/switch_ssd_check/test_switch_ssd_check.py new file mode 100644 index 00000000..1f7202f5 --- /dev/null +++ b/tests/checks/switch_ssd_check/test_switch_ssd_check.py @@ -0,0 +1,54 @@ +import os +import pytest +import logging +import importlib +from helpers.utils import read_data + +script = importlib.import_module("aci-preupgrade-validation-script") +Result = script.Result + +log = logging.getLogger(__name__) +dir = os.path.dirname(os.path.abspath(__file__)) + +test_function = "switch_ssd_check" + +# icurl queries +faultInst = 'faultInst.json?query-target-filter=or(eq(faultInst.code,"F3073"),eq(faultInst.code,"F3074"))' + + +@pytest.mark.parametrize( + "icurl_outputs, expected_result, expected_data", + [ + ( + {faultInst: []}, + script.PASS, + [], + ), + ( + {faultInst: read_data(dir, "faultInst.json")}, + script.FAIL_O, + [ + [ + "F3073", + "1", + "205", + "Micron_M550_MTFDDAT256MAY", + "90%", + "Contact Cisco TAC for replacement procedure", + ], + [ + "F3074", + "1", + "101", + "Micron_M600_MTFDDAT064MBF", + "80%", + "Monitor (no impact to upgrades)", + ], + ], + ), + ], +) +def test_logic(run_check, mock_icurl, expected_result, expected_data): + result = run_check() + assert result.result == expected_result + assert result.data == expected_data From 1695d89abdf7f3e090c654425eaae538502cc97f Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Tue, 4 Nov 2025 16:21:08 -0800 Subject: [PATCH 03/16] fix: MANUAL instead of ERROR when switch bootflash objects not found --- aci-preupgrade-validation-script.py | 2 +- .../test_switch_bootflash_usage_check.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 583a165e..5793222f 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -2050,7 +2050,7 @@ def switch_bootflash_usage_check(tversion, **kwargs): partitions = icurl('class', partitions_api) if not partitions: - return Result(result=ERROR, msg='bootflash objects not found', doc_url=doc_url) + return Result(result=MANUAL, msg='bootflash objects not found. Check switch health.', doc_url=doc_url) predownloaded_nodes = [] try: diff --git a/tests/checks/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py b/tests/checks/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py index 0b85f163..18e9c1c3 100644 --- a/tests/checks/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py +++ b/tests/checks/switch_bootflash_usage_check/test_switch_bootflash_usage_check.py @@ -23,6 +23,14 @@ @pytest.mark.parametrize( "icurl_outputs, tversion, expected_result", [ + ( + { + partitions: [], + download_sts: [], + }, + "6.0(2h)", + script.MANUAL, + ), ( { partitions: read_data(dir, "eqptcapacityFSPartition.json"), From ad4c1e40382b2223091601e638d59d87a393c329 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Tue, 4 Nov 2025 17:19:16 -0800 Subject: [PATCH 04/16] fix: MANUAL instead of ERROR when switch sup objects not found --- aci-preupgrade-validation-script.py | 2 +- tests/checks/sup_hwrev_check/test_sup_hwrev_check.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 5793222f..cedaffaf 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -4045,7 +4045,7 @@ def sup_hwrev_check(cversion, tversion, **kwargs): sup_re = r'/.+(?P<supslot>supslot-\d+)' sups = icurl('class', 'eqptSpCmnBlk.json?&query-target-filter=wcard(eqptSpromSupBlk.dn,"sup")') if not sups: - return Result(result=ERROR, msg='No sups found. This is unlikely.') + return Result(result=MANUAL, msg='No sups found. This is unlikely. Check switch health.') for sup in sups: prtNum = sup['eqptSpCmnBlk']['attributes']['prtNum'] diff --git a/tests/checks/sup_hwrev_check/test_sup_hwrev_check.py b/tests/checks/sup_hwrev_check/test_sup_hwrev_check.py index c8094296..80ed301b 100644 --- a/tests/checks/sup_hwrev_check/test_sup_hwrev_check.py +++ b/tests/checks/sup_hwrev_check/test_sup_hwrev_check.py @@ -18,6 +18,13 @@ @pytest.mark.parametrize( "icurl_outputs, cversion, tversion, expected_result", [ + # Affected versions. No Sups found + ( + {eqptSpCmnBlk: []}, + "5.2(1g)", + "5.2(8e)", + script.MANUAL, + ), # Affected Sups and on 5.2. VRM and FPGA Concern ( {eqptSpCmnBlk: read_data(dir, "eqptSpCmnBlk_POS.json")}, From c9acd99cbeb1be92e9e77d4b97a5b2a8d0362ab8 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Tue, 4 Nov 2025 18:48:24 -0800 Subject: [PATCH 05/16] bump to v3.4.13 --- aci-preupgrade-validation-script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index cedaffaf..a57eceb0 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -38,7 +38,7 @@ import os import re -SCRIPT_VERSION = "v3.4.12" +SCRIPT_VERSION = "v3.4.13" DEFAULT_TIMEOUT = 600 # sec # result constants DONE = 'DONE' From f017a0dc86275806ba10580e95177c7d1fdca887 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Sun, 16 Nov 2025 22:39:54 -0800 Subject: [PATCH 06/16] feat: Add fabricNode in common_data --- aci-preupgrade-validation-script.py | 354 +++--- tests/checks/apic_ssd_check/fabricNode.json | 93 ++ .../apic_ssd_check/fabricNode_no_apic.json | 48 + tests/checks/apic_ssd_check/fault_F2731.json | 12 + .../apic_ssd_check/test_apic_ssd_check.py | 194 ++++ .../apic_version_md5_check/fabricNode.json | 93 ++ .../fabricNode_no_apic.json | 48 + .../test_apic_version_md5_check.py | 118 +- .../apic_version_md5_check/topSystem.json | 1023 ----------------- .../fabricNode.json | 217 ++-- .../test_fabric_link_redundancy_check.py | 51 +- .../fabricdomain_name_check/fabricNode.json | 108 ++ .../fabricNode_no_apic1.json | 93 ++ .../test_fabricdomain_name_check.py | 57 +- .../topSystem_1POS.json | 22 - .../topSystem_2POS.json | 22 - .../topSystem_NEG.json | 22 - .../fc_ex_model_check/fabricNode_NEG.json | 108 +- .../fc_ex_model_check/fabricNode_POS.json | 146 ++- .../fc_ex_model_check/fcEntity_101_102.json | 18 + .../fcEntity_101_102_103.json | 26 + .../fc_ex_model_check/fcEntity_104.json | 10 + .../fc_ex_model_check/fcEntity_NEG.json | 1 - .../fc_ex_model_check/fcEntity_POS.json | 18 - .../test_fc_ex_model_check.py | 121 +- .../fabricNode_no_gen1.json | 30 + .../fabricNode_with_gen1.json | 44 + .../test_gen1_switch_compatibility_check.py | 42 + .../fabricNode_all_phys_apic.json | 92 ++ .../fabricNode_mini_aci.json | 92 ++ .../test_mini_aci_6_0_2_check.py | 53 +- .../fabricNode_FX3H.json | 55 +- .../fabricNode_FX3P.json | 61 +- .../fabricNode_FX3P3H.json | 51 +- .../fabricNode_no_FX3P3H.json | 47 + ..._n9k_c93108tc_fx3p_interface_down_check.py | 86 +- .../ntp_status_check/NEG_datetimeClkPol.json | 46 + .../ntp_status_check/NEG_datetimeNtpq.json | 29 + .../ntp_status_check/POS_datetimeClkPol.json | 46 + .../ntp_status_check/POS_datetimeNtpq.json | 29 + tests/checks/ntp_status_check/fabricNode.json | 64 ++ .../ntp_status_check/test_ntp_status_check.py | 65 ++ .../observer_db_size_check/fabricNode.json | 46 + .../fabricNode_no_apic.json | 13 + .../test_observer_db_size_check.py | 60 +- .../observer_db_size_check/topSystem.json | 35 - .../topSystem_empty.json | 1 - .../fabricNode_no_RL.json | 123 ++ .../fabricNode_with_RL.json | 145 +++ .../infraSetPol_DTF_disabled.json | 13 + .../infraSetPol_DTF_enabled.json | 13 + .../infraSetPol_no_DTF.json | 12 + .../test_r_leaf_compatibility_check.py | 103 ++ .../fabricNode.json | 46 + .../fabricRsDecommissionNode_NEG.json | 1 - .../test_stale_decomissioned_spine_check.py | 35 +- .../topSystem.json | 24 - .../bgpRRNodePEp_1001_1002_2001_2002.json | 38 + .../fabricExplicitGEp.json | 92 ++ .../fabricNode.json | 206 ++++ ...extRsNodeL3OutAtt_1001_1002_2001_2002.json | 50 + ...extRsNodeL3OutAtt_1003_1004_2001_2002.json | 50 + .../lldpCtrlrAdjEp.json | 62 + .../maintMaintGrp_ALL.json | 94 ++ .../maintMaintGrp_BAD_GRP1_GRP2.json | 116 ++ .../maintMaintGrp_BAD_ONLY_POD1_SPINE_RR.json | 160 +++ .../maintMaintGrp_EVEN_ODD.json | 116 ++ .../maintMaintGrp_SPINE_LEAF.json | 116 ++ .../test_switch_group_guideline_check.py | 193 ++++ .../switch_status_check/fabricNode_NEG.json | 122 ++ .../switch_status_check/fabricNode_POS.json | 122 ++ .../fabricRsDecommissionNode.json | 12 + .../test_switch_status_check.py | 46 + .../vpc_paired_switches_check/fabricNode.json | 145 +++ .../test_vpc_paired_switches_check.py | 21 +- .../vpc_paired_switches_check/topSystem.json | 134 --- tests/conftest.py | 131 +++ tests/test_common_data.py | 225 +++- 78 files changed, 5033 insertions(+), 1843 deletions(-) create mode 100644 tests/checks/apic_ssd_check/fabricNode.json create mode 100644 tests/checks/apic_ssd_check/fabricNode_no_apic.json create mode 100644 tests/checks/apic_ssd_check/fault_F2731.json create mode 100644 tests/checks/apic_ssd_check/test_apic_ssd_check.py create mode 100644 tests/checks/apic_version_md5_check/fabricNode.json create mode 100644 tests/checks/apic_version_md5_check/fabricNode_no_apic.json delete mode 100644 tests/checks/apic_version_md5_check/topSystem.json create mode 100644 tests/checks/fabricdomain_name_check/fabricNode.json create mode 100644 tests/checks/fabricdomain_name_check/fabricNode_no_apic1.json create mode 100644 tests/checks/fc_ex_model_check/fcEntity_101_102.json create mode 100644 tests/checks/fc_ex_model_check/fcEntity_101_102_103.json create mode 100644 tests/checks/fc_ex_model_check/fcEntity_104.json delete mode 100644 tests/checks/fc_ex_model_check/fcEntity_NEG.json delete mode 100644 tests/checks/fc_ex_model_check/fcEntity_POS.json create mode 100644 tests/checks/gen1_switch_compatibility_check/fabricNode_no_gen1.json create mode 100644 tests/checks/gen1_switch_compatibility_check/fabricNode_with_gen1.json create mode 100644 tests/checks/gen1_switch_compatibility_check/test_gen1_switch_compatibility_check.py create mode 100644 tests/checks/mini_aci_6_0_2_check/fabricNode_all_phys_apic.json create mode 100644 tests/checks/mini_aci_6_0_2_check/fabricNode_mini_aci.json create mode 100644 tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_no_FX3P3H.json create mode 100644 tests/checks/ntp_status_check/NEG_datetimeClkPol.json create mode 100644 tests/checks/ntp_status_check/NEG_datetimeNtpq.json create mode 100644 tests/checks/ntp_status_check/POS_datetimeClkPol.json create mode 100644 tests/checks/ntp_status_check/POS_datetimeNtpq.json create mode 100644 tests/checks/ntp_status_check/fabricNode.json create mode 100644 tests/checks/ntp_status_check/test_ntp_status_check.py create mode 100644 tests/checks/observer_db_size_check/fabricNode.json create mode 100644 tests/checks/observer_db_size_check/fabricNode_no_apic.json delete mode 100644 tests/checks/observer_db_size_check/topSystem.json delete mode 100644 tests/checks/observer_db_size_check/topSystem_empty.json create mode 100644 tests/checks/r_leaf_compatibility_check/fabricNode_no_RL.json create mode 100644 tests/checks/r_leaf_compatibility_check/fabricNode_with_RL.json create mode 100644 tests/checks/r_leaf_compatibility_check/infraSetPol_DTF_disabled.json create mode 100644 tests/checks/r_leaf_compatibility_check/infraSetPol_DTF_enabled.json create mode 100644 tests/checks/r_leaf_compatibility_check/infraSetPol_no_DTF.json create mode 100644 tests/checks/r_leaf_compatibility_check/test_r_leaf_compatibility_check.py create mode 100644 tests/checks/stale_decomissioned_spine_check/fabricNode.json delete mode 100644 tests/checks/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json delete mode 100644 tests/checks/stale_decomissioned_spine_check/topSystem.json create mode 100644 tests/checks/switch_group_guideline_check/bgpRRNodePEp_1001_1002_2001_2002.json create mode 100644 tests/checks/switch_group_guideline_check/fabricExplicitGEp.json create mode 100644 tests/checks/switch_group_guideline_check/fabricNode.json create mode 100644 tests/checks/switch_group_guideline_check/l3extRsNodeL3OutAtt_1001_1002_2001_2002.json create mode 100644 tests/checks/switch_group_guideline_check/l3extRsNodeL3OutAtt_1003_1004_2001_2002.json create mode 100644 tests/checks/switch_group_guideline_check/lldpCtrlrAdjEp.json create mode 100644 tests/checks/switch_group_guideline_check/maintMaintGrp_ALL.json create mode 100644 tests/checks/switch_group_guideline_check/maintMaintGrp_BAD_GRP1_GRP2.json create mode 100644 tests/checks/switch_group_guideline_check/maintMaintGrp_BAD_ONLY_POD1_SPINE_RR.json create mode 100644 tests/checks/switch_group_guideline_check/maintMaintGrp_EVEN_ODD.json create mode 100644 tests/checks/switch_group_guideline_check/maintMaintGrp_SPINE_LEAF.json create mode 100644 tests/checks/switch_group_guideline_check/test_switch_group_guideline_check.py create mode 100644 tests/checks/switch_status_check/fabricNode_NEG.json create mode 100644 tests/checks/switch_status_check/fabricNode_POS.json create mode 100644 tests/checks/switch_status_check/fabricRsDecommissionNode.json create mode 100644 tests/checks/switch_status_check/test_switch_status_check.py create mode 100644 tests/checks/vpc_paired_switches_check/fabricNode.json delete mode 100644 tests/checks/vpc_paired_switches_check/topSystem.json diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index a57eceb0..295169f9 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -38,7 +38,7 @@ import os import re -SCRIPT_VERSION = "v3.4.13" +SCRIPT_VERSION = "v3.4.14" DEFAULT_TIMEOUT = 600 # sec # result constants DONE = 'DONE' @@ -1633,27 +1633,6 @@ def get_credentials(): return usr, pwd -def get_current_version(arg_cversion): - """ Returns: AciVersion instance """ - if arg_cversion: - prints("Current APIC version is overridden to %s" % arg_cversion) - try: - current_version = AciVersion(arg_cversion) - except ValueError as e: - prints(e) - sys.exit(1) - return current_version - prints("Checking current APIC version...", end='') - firmwares = icurl('class', 'firmwareCtrlrRunning.json') - for firmware in firmwares: - if 'node-1' in firmware['firmwareCtrlrRunning']['attributes']['dn']: - apic1_version = firmware['firmwareCtrlrRunning']['attributes']['version'] - break - current_version = AciVersion(apic1_version) - prints('%s\n' % current_version) - return current_version - - def get_target_version(arg_tversion): """ Returns: AciVersion instance """ if arg_tversion: @@ -1696,6 +1675,55 @@ def get_target_version(arg_tversion): return None +def get_fabric_nodes(): + """Returns list of fabricNode objects. + + Using fabricNode instead of topSystem because topSystem times out when the + node is inactive. + """ + prints("Gathering Node Information...\n") + fabricNodes = icurl('class', 'fabricNode.json') + return fabricNodes + + +def get_current_versions(fabric_nodes, arg_cversion): + """ Returns: AciVersion instances of APIC and lowest switch """ + if arg_cversion: + prints("Current version is overridden to %s" % arg_cversion) + try: + current_version = AciVersion(arg_cversion) + except ValueError as e: + prints(e) + sys.exit(1) + return current_version, current_version + + apic_version = "" # There can be only one APIC version + switch_versions = set() + for node in fabric_nodes: + version = node["fabricNode"]["attributes"]["version"] + if not version: + continue + if node["fabricNode"]["attributes"]["role"] == "controller": + apic_version = AciVersion(version) + else: + switch_versions.add(version) + + prints("Current APIC Version...{}".format(apic_version)) + + msg = "Lowest Switch Version...{}" + if not switch_versions: + prints(msg.format("Not Found! Join switches to the fabric then re-run this script.\n")) + return apic_version, None + + lowest_sw_ver = AciVersion(switch_versions.pop()) + for sw_version in switch_versions: + sw_version = AciVersion(sw_version) + if lowest_sw_ver.newer_than(sw_version): + lowest_sw_ver = sw_version + prints(msg.format(lowest_sw_ver) + "\n") + return apic_version, lowest_sw_ver + + def get_vpc_nodes(): """ Returns list of VPC Node IDs; ['101', '102', etc...] """ prints("Collecting VPC Node IDs...", end='') @@ -1714,37 +1742,15 @@ def get_vpc_nodes(): return vpc_nodes -def get_switch_version(): - """ Returns lowest switch version as AciVersion instance """ - prints("Gathering Lowest Switch Version from Firmware Repository...", end='') - firmwares = icurl('class', 'firmwareRunning.json') - versions = set() - - for firmware in firmwares: - versions.add(firmware['firmwareRunning']['attributes']['peVer']) - - if versions: - lowest_sw_ver = AciVersion(versions.pop()) - for version in versions: - version = AciVersion(version) - if lowest_sw_ver.newer_than(str(version)): - lowest_sw_ver = version - prints('%s\n' % lowest_sw_ver) - return lowest_sw_ver - else: - prints("No Switches Detected! Join switches to the fabric then re-run this script.\n") - return None - - def query_common_data(api_only=False, arg_cversion=None, arg_tversion=None): username = password = None if not api_only: username, password = get_credentials() try: - cversion = get_current_version(arg_cversion) + fabric_nodes = get_fabric_nodes() + cversion, sw_cversion = get_current_versions(fabric_nodes, arg_cversion) tversion = get_target_version(arg_tversion) - sw_cversion = get_switch_version() vpc_nodes = get_vpc_nodes() except Exception as e: prints('\n\nError: %s' % e) @@ -1758,6 +1764,7 @@ def query_common_data(api_only=False, arg_cversion=None, arg_tversion=None): 'cversion': cversion, 'tversion': tversion, 'sw_cversion': sw_cversion, + 'fabric_nodes': fabric_nodes, 'vpc_node_ids': vpc_nodes, } @@ -1797,29 +1804,30 @@ def apic_cluster_health_check(cversion, **kwargs): @check_wrapper(check_title="Switch Fabric Membership Status") -def switch_status_check(**kwargs): +def switch_status_check(fabric_nodes, **kwargs): result = FAIL_UF msg = '' headers = ['Pod-ID', 'Node-ID', 'State'] data = [] - recommended_action = 'Bring this node back to "active"' + recommended_action = 'Bring these nodes back to "active"' # fabricNode.fabricSt shows `disabled` for both Decommissioned and Maintenance (GIR). # fabricRsDecommissionNode.debug==yes is required to show `disabled (Maintenance)`. - fabricNodes = icurl('class', 'fabricNode.json?&query-target-filter=ne(fabricNode.role,"controller")') girNodes = icurl('class', 'fabricRsDecommissionNode.json?&query-target-filter=eq(fabricRsDecommissionNode.debug,"yes")') - for fabricNode in fabricNodes: - state = fabricNode['fabricNode']['attributes']['fabricSt'] + for fabric_node in fabric_nodes: + if fabric_node['fabricNode']['attributes']['role'] == "controller": + continue + state = fabric_node['fabricNode']['attributes']['fabricSt'] if state == 'active': continue - dn = re.search(node_regex, fabricNode['fabricNode']['attributes']['dn']) + dn = re.search(node_regex, fabric_node['fabricNode']['attributes']['dn']) pod_id = dn.group("pod") node_id = dn.group("node") for gir in girNodes: if node_id == gir['fabricRsDecommissionNode']['attributes']['targetId']: state = state + ' (Maintenance)' data.append([pod_id, node_id, state]) - if not fabricNodes: + if not fabric_nodes: result = MANUAL msg = 'Switch fabricNode not found!' elif not data: @@ -1853,14 +1861,13 @@ def maintp_grp_crossing_4_0_check(cversion, tversion, **kwargs): @check_wrapper(check_title="NTP Status") -def ntp_status_check(**kargs): +def ntp_status_check(fabric_nodes, **kargs): result = FAIL_UF headers = ["Pod-ID", "Node-ID"] data = [] recommended_action = 'Not Synchronized. Check NTP config and NTP server reachability.' doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#ntp-status" - fabricNodes = icurl('class', 'fabricNode.json') - nodes = [fn['fabricNode']['attributes']['id'] for fn in fabricNodes] + nodes = [fn['fabricNode']['attributes']['id'] for fn in fabric_nodes] apicNTPs = icurl('class', 'datetimeNtpq.json') switchNTPs = icurl('class', 'datetimeClkPol.json') for apicNTP in apicNTPs: @@ -1873,7 +1880,7 @@ def ntp_status_check(**kargs): dn = re.search(node_regex, switchNTP['datetimeClkPol']['attributes']['dn']) if dn and dn.group('node') in nodes: nodes.remove(dn.group('node')) - for fn in fabricNodes: + for fn in fabric_nodes: if fn['fabricNode']['attributes']['id'] in nodes: dn = re.search(node_regex, fn['fabricNode']['attributes']['dn']) data.append([dn.group('pod'), dn.group('node')]) @@ -1925,7 +1932,7 @@ def features_to_disable_check(cversion, tversion, **kwargs): @check_wrapper(check_title="Switch Upgrade Group Guidelines") -def switch_group_guideline_check(**kwargs): +def switch_group_guideline_check(fabric_nodes, **kwargs): result = FAIL_O headers = ['Group Name', 'Pod-ID', 'Node-IDs', 'Failure Reason'] data = [] @@ -1944,8 +1951,7 @@ def switch_group_guideline_check(**kwargs): reason_vpc = 'Both leaf nodes in the same vPC pair are in the same group.' nodes = {} - fabricNodes = icurl('class', 'fabricNode.json') - for fn in fabricNodes: + for fn in fabric_nodes: attr = fn['fabricNode']['attributes'] nodes[attr['dn']] = {'role': attr['role'], 'nodeType': attr['nodeType']} @@ -2024,6 +2030,7 @@ def switch_group_guideline_check(**kwargs): 'pod': vpc_peer['fabricNodePEp']['attributes']['podId'] }) if len(m_vpc_peers) > 1: + m_vpc_peers.sort(key=lambda d: d['node']) data.append([m_name, m_vpc_peers[0]['pod'], ','.join(x['node'] for x in m_vpc_peers), reason_vpc]) @@ -2575,32 +2582,66 @@ def switch_ssd_check(**kwargs): # Connection Based Check @check_wrapper(check_title="APIC SSD Health") -def apic_ssd_check(cversion, username, password, **kwargs): +def apic_ssd_check(cversion, username, password, fabric_nodes, **kwargs): result = FAIL_UF headers = ["Pod", "Node", "Storage Unit", "% lifetime remaining", "Recommended Action"] data = [] - unformatted_headers = ["Fault", "Fault DN", "% lifetime remaining", "Recommended Action"] + unformatted_headers = ["Fault DN", "% lifetime remaining", "Recommended Action"] unformatted_data = [] recommended_action = "Contact TAC for replacement" doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#apic-ssd-health" - has_error = False dn_regex = node_regex + r'/.+p-\[(?P<storage>.+)\]-f' - faultInsts = icurl('class', 'faultInst.json?query-target-filter=eq(faultInst.code,"F2731")') - if len(faultInsts) == 0 and (cversion.older_than("4.2(7f)") or cversion.older_than("5.2(1g)")): - controller = icurl('class', 'topSystem.json?query-target-filter=eq(topSystem.role,"controller")') - if not controller: - return Result(result=ERROR, msg="topSystem response empty. Is the cluster healthy?", doc_url=doc_url) + threshold = {"F2731": "<5% (Fault F2731)", "F2732": "<1% (Fault F2732)"} + # Not checking F0101 because if APIC SSD is not operaitonal, the given APIC + # does not work at all and APIC clustering should be broken. + faultInsts = icurl('class', 'faultInst.json?query-target-filter=or(eq(faultInst.code,"F2731"),eq(faultInst.code,"F2732"))') + for faultInst in faultInsts: + code = faultInst["faultInst"]["attributes"]["code"] + lifetime_remaining = threshold.get(code, "unknown") + dn_array = re.search(dn_regex, faultInst['faultInst']['attributes']['dn']) + if dn_array: + data.append([ + dn_array.group("pod"), + dn_array.group("node"), + dn_array.group("storage"), + lifetime_remaining, + recommended_action, + ]) + else: + unformatted_data.append([ + faultInst['faultInst']['attributes']['dn'], + lifetime_remaining, + recommended_action, + ]) + + # Versions older than 4.2(7f) or 5.x - 5.2(1g) may fail to raise F273x. + # Check logs for those just in case. + has_error = False + if ( + len(faultInsts) == 0 + and ( + cversion.older_than("4.2(7f)") + or (cversion.major1 == "5" and cversion.older_than("5.2(1g)")) + ) + ): + apics = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["role"] == "controller"] + if not apics: + return Result(result=ERROR, msg="No fabricNode of APIC. Is the cluster healthy?", doc_url=doc_url) - print('') report_other = False checked_apics = {} - for apic in controller: - attr = apic['topSystem']['attributes'] + for apic in apics: + attr = apic['fabricNode']['attributes'] if attr['address'] in checked_apics: continue checked_apics[attr['address']] = 1 - pod_id = attr['podId'] - node_id = attr['id'] + dn = re.search(node_regex, attr['dn']) + if dn: + pod_id = dn.group('pod') + node_id = dn.group('node') + else: + pod_id = "--" + node_id = attr['id'] try: c = Connection(attr['address']) c.username = username @@ -2608,14 +2649,13 @@ def apic_ssd_check(cversion, username, password, **kwargs): c.log = LOG_FILE c.connect() except Exception as e: - data.append([attr['id'], attr['name'], '-', '-', str(e)]) + data.append([pod_id, node_id, '-', '-', str(e)]) has_error = True continue try: - c.cmd( - 'grep -oE "SSD Wearout Indicator is [0-9]+" /var/log/dme/log/svc_ifc_ae.bin.log | tail -1') + c.cmd('grep -oE "SSD Wearout Indicator is [0-9]+" /var/log/dme/log/svc_ifc_ae.bin.log | tail -1') except Exception as e: - data.append([attr['id'], attr['name'], '-', '-', str(e)]) + data.append([pod_id, node_id, '-', '-', str(e)]) has_error = True continue @@ -2628,17 +2668,6 @@ def apic_ssd_check(cversion, username, password, **kwargs): continue if report_other: data.append([pod_id, node_id, "Solid State Disk", wearout, "No Action Required"]) - else: - headers = ["Fault", "Pod", "Node", "Storage Unit", "% lifetime remaining", "Recommended Action"] - for faultInst in faultInsts: - dn_array = re.search(dn_regex, faultInst['faultInst']['attributes']['dn']) - lifetime_remaining = "<5%" - if dn_array: - data.append(['F2731', dn_array.group("pod"), dn_array.group("node"), dn_array.group("storage"), - lifetime_remaining, recommended_action]) - else: - unformatted_data.append( - ['F2731', faultInst['faultInst']['attributes']['dn'], lifetime_remaining, recommended_action]) if has_error: result = ERROR elif not data and not unformatted_data: @@ -3220,7 +3249,7 @@ def lldp_with_infra_vlan_mismatch_check(**kwargs): # Connection Based Check @check_wrapper(check_title="APIC Target version image and MD5 hash") -def apic_version_md5_check(tversion, username, password, **kwargs): +def apic_version_md5_check(tversion, username, password, fabric_nodes, **kwargs): result = FAIL_UF headers = ['APIC', 'Firmware', 'md5sum', 'Failure'] data = [] @@ -3246,14 +3275,15 @@ def apic_version_md5_check(tversion, username, password, **kwargs): md5s = [] md5_names = [] + apics = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["role"] == "controller"] + if not apics: + return Result(result=ERROR, msg="No fabricNode of APIC. Is the cluster healthy?", doc_url=doc_url) + has_error = False - nodes_response_json = icurl('class', 'topSystem.json') - for node in nodes_response_json: - if node['topSystem']['attributes']['role'] != "controller": - continue - apic_name = node['topSystem']['attributes']['name'] + for apic in apics: + apic_name = apic['fabricNode']['attributes']['name'] try: - c = Connection(node['topSystem']['attributes']['address']) + c = Connection(apic['fabricNode']['attributes']['address']) c.username = username c.password = password c.log = LOG_FILE @@ -3367,7 +3397,7 @@ def standby_apic_disk_space_check(**kwargs): @check_wrapper(check_title="Remote Leaf Compatibility") -def r_leaf_compatibility_check(tversion, **kwargs): +def r_leaf_compatibility_check(tversion, fabric_nodes, **kwargs): result = PASS headers = ['Target Version', 'Remote Leaf', 'Direct Traffic Forwarding'] data = [] @@ -3380,7 +3410,7 @@ def r_leaf_compatibility_check(tversion, **kwargs): if not tversion: return Result(result=MANUAL, msg=TVER_MISSING) - remote_leafs = icurl('class', 'fabricNode.json?&query-target-filter=eq(fabricNode.nodeType,"remote-leaf-wan")') + remote_leafs = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["nodeType"] == "remote-leaf-wan"] if not remote_leafs: return Result(result=NA, msg="No Remote Leaf Found") @@ -3497,20 +3527,19 @@ def vmm_controller_adj_check(**kwargs): @check_wrapper(check_title="VPC-paired Leaf switches") -def vpc_paired_switches_check(vpc_node_ids, **kwargs): +def vpc_paired_switches_check(vpc_node_ids, fabric_nodes, **kwargs): result = PASS headers = ["Node ID", "Node Name"] data = [] recommended_action = 'Determine if dataplane redundancy is available if these nodes go down.' doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#vpc-paired-leaf-switches' - top_system = icurl('class', 'topSystem.json') - for node in top_system: - node_id = node['topSystem']['attributes']['id'] - role = node['topSystem']['attributes']['role'] + for node in fabric_nodes: + node_id = node['fabricNode']['attributes']['id'] + role = node['fabricNode']['attributes']['role'] if role == 'leaf' and (node_id not in vpc_node_ids): result = MANUAL - name = node['topSystem']['attributes']['name'] + name = node['fabricNode']['attributes']['name'] data.append([node_id, name]) return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) @@ -3738,7 +3767,7 @@ def target_version_compatibility_check(cversion, tversion, **kwargs): @check_wrapper(check_title="Gen 1 switch compatibility") -def gen1_switch_compatibility_check(tversion, **kwargs): +def gen1_switch_compatibility_check(tversion, fabric_nodes, **kwargs): result = FAIL_UF headers = ["Target Version", "Node ID", "Model", "Warning"] gen1_models = ["N9K-C9336PQ", "N9K-X9736PQ", "N9K-C9504-FM", "N9K-C9508-FM", "N9K-C9516-FM", "N9K-C9372PX-E", @@ -3746,13 +3775,12 @@ def gen1_switch_compatibility_check(tversion, **kwargs): "N9K-C93128TX"] data = [] recommended_action = 'Select supported target version or upgrade hardware' - doc_url = 'http://cs.co/9001ydKCV' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#compatibility-switch-hardware-gen1' if not tversion: return Result(result=MANUAL, msg=TVER_MISSING) if tversion.newer_than("5.0(1a)"): - fabric_node = icurl('class', 'fabricNode.json') - for node in fabric_node: + for node in fabric_nodes: if node['fabricNode']['attributes']['model'] in gen1_models: data.append([str(tversion), node['fabricNode']['attributes']['id'], node['fabricNode']['attributes']['model'], 'Not supported on 5.x+']) @@ -3993,22 +4021,26 @@ def apic_ca_cert_validation(**kwargs): @check_wrapper(check_title="FabricDomain Name") -def fabricdomain_name_check(cversion, tversion, **kwargs): +def fabricdomain_name_check(cversion, tversion, fabric_nodes, **kwargs): result = FAIL_O headers = ["FabricDomain", "Reason"] data = [] recommended_action = "Do not upgrade to 6.0(2)" - doc_url = 'https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwf80352' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#fabricdomain-name' if not tversion: return Result(result=MANUAL, msg=TVER_MISSING) if tversion.same_as("6.0(2h)"): - controller = icurl('class', 'topSystem.json?query-target-filter=eq(topSystem.role,"controller")') - if not controller: - return Result(result=ERROR, msg='topSystem response empty. Is the cluster healthy?') - - fabricDomain = controller[0]['topSystem']['attributes']['fabricDomain'] + apic1 = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["id"] == "1"] + if not apic1: + return Result(result=ERROR, msg='No fabricNode of APIC 1. Is the cluster healthy?') + + # Using topSystem because fabricTopology.fabricDomain is not yet available prior to 5.2(6e). + apic1_topsys = icurl("mo", "/".join([apic1[0]["fabricNode"]["attributes"]["dn"], "sys.json"])) + if not apic1_topsys: + return Result(result=ERROR, msg='No topSystem of APIC 1. Is the cluster healthy?') + fabricDomain = apic1_topsys[0]['topSystem']['attributes']['fabricDomain'] if re.search(r'#|;', fabricDomain): data.append([fabricDomain, "Contains a special character"]) @@ -4154,25 +4186,28 @@ def oob_mgmt_security_check(cversion, tversion, **kwargs): @check_wrapper(check_title="Mini ACI Upgrade to 6.0(2)+") -def mini_aci_6_0_2_check(cversion, tversion, **kwargs): +def mini_aci_6_0_2_check(cversion, tversion, fabric_nodes, **kwargs): result = FAIL_UF - headers = ["Pod ID", "Node ID", "APIC Type", "Failure Reason"] + headers = ["Node ID", "Node Name", "APIC Type"] data = [] recommended_action = "All virtual APICs must be removed from the cluster prior to upgrading to 6.0(2)+." - doc_url = 'Upgrading Mini ACI - http://cs.co/9009bBTQB' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#mini-aci-upgrade-to-602-or-later' if not tversion: return Result(result=MANUAL, msg=TVER_MISSING) - if cversion.older_than("6.0(2a)") and tversion.newer_than("6.0(2a)"): - topSystem = icurl('class', 'topSystem.json?query-target-filter=wcard(topSystem.role,"controller")') - if not topSystem: - return Result(result=ERROR, msg='topSystem response empty. Is the cluster healthy?') - for controller in topSystem: - if controller['topSystem']['attributes']['nodeType'] == "virtual": - pod_id = controller["topSystem"]["attributes"]["podId"] - node_id = controller['topSystem']['attributes']['id'] - data.append([pod_id, node_id, "virtual", "Virtual APIC must be removed prior to upgrade to 6.0(2)+"]) + if not (cversion.older_than("6.0(2a)") and tversion.newer_than("6.0(2a)")): + return Result(result=NA, msg=VER_NOT_AFFECTED, doc_url=doc_url) + + apics = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["role"] == "controller"] + if not apics: + return Result(result=ERROR, msg="No fabricNode of APIC. Is the cluster healthy?", doc_url=doc_url) + + for apic in apics: + if apic['fabricNode']['attributes']['nodeType'] == "virtual": + node_id = apic['fabricNode']['attributes']['id'] + node_name = apic['fabricNode']['attributes']['name'] + data.append([node_id, node_name, "virtual"]) if not data: result = PASS @@ -4443,7 +4478,7 @@ def fabric_dpp_check(tversion, **kwargs): @check_wrapper(check_title='N9K-C93108TC-FX3P/FX3H Interface Down') -def n9k_c93108tc_fx3p_interface_down_check(tversion, **kwargs): +def n9k_c93108tc_fx3p_interface_down_check(tversion, fabric_nodes, **kwargs): result = PASS headers = ["Node ID", "Node Name", "Product ID"] data = [] @@ -4458,12 +4493,9 @@ def n9k_c93108tc_fx3p_interface_down_check(tversion, **kwargs): or tversion.same_as("5.3(1d)") or (tversion.major1 == "6" and tversion.older_than("6.0(4a)")) ): - api = 'fabricNode.json' - api += '?query-target-filter=or(' - api += 'eq(fabricNode.model,"N9K-C93108TC-FX3P"),' - api += 'eq(fabricNode.model,"N9K-C93108TC-FX3H"))' - nodes = icurl('class', api) - for node in nodes: + for node in fabric_nodes: + if node["fabricNode"]["attributes"]["model"] not in ["N9K-C93108TC-FX3P", "N9K-C93108TC-FX3H"]: + continue nodeid = node["fabricNode"]["attributes"]["id"] name = node["fabricNode"]["attributes"]["name"] pid = node["fabricNode"]["attributes"]["model"] @@ -5117,7 +5149,7 @@ def validate_32_64_bit_image_check(cversion, tversion, **kwargs): @check_wrapper(check_title='Fabric Link Redundancy') -def fabric_link_redundancy_check(**kwargs): +def fabric_link_redundancy_check(fabric_nodes, **kwargs): result = PASS headers = ["Leaf Name", "Fabric Link Adjacencies", "Problem"] data = [] @@ -5126,20 +5158,18 @@ def fabric_link_redundancy_check(**kwargs): t1_recommended_action = "Connect the tier 2 leaf switch(es) to multiple tier1 leaf switches for redundancy" doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#fabric-link-redundancy" - fabric_nodes_api = 'fabricNode.json' - fabric_nodes_api += '?query-target-filter=and(or(eq(fabricNode.role,"leaf"),eq(fabricNode.role,"spine")),eq(fabricNode.fabricSt,"active"))' - lldp_adj_api = 'lldpAdjEp.json' lldp_adj_api += '?query-target-filter=wcard(lldpAdjEp.sysDesc,"topology/pod")' - fabricNodes = icurl("class", fabric_nodes_api) spines = {} leafs = {} t2leafs = {} - for node in fabricNodes: + for node in fabric_nodes: if node["fabricNode"]["attributes"]["nodeType"] == "remote-leaf-wan": # Not applicable to remote leafs, skip continue + if node["fabricNode"]["attributes"]["fabricSt"] != "active": + continue dn = node["fabricNode"]["attributes"]["dn"] name = node["fabricNode"]["attributes"]["name"] if node["fabricNode"]["attributes"]["role"] == "spine": @@ -5253,7 +5283,7 @@ def out_of_service_ports_check(**kwargs): @check_wrapper(check_title='FC/FCOE support removed for -EX platforms') -def fc_ex_model_check(tversion, **kwargs): +def fc_ex_model_check(tversion, fabric_nodes, **kwargs): result = PASS headers = ["FC/FCOE Node ID", "Model"] data = [] @@ -5261,8 +5291,6 @@ def fc_ex_model_check(tversion, **kwargs): doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#fcfcoe-support-for-ex-switches' fcEntity_api = "fcEntity.json" - fabricNode_api = 'fabricNode.json' - fabricNode_api += '?query-target-filter=wcard(fabricNode.model,".*EX")' if not tversion: return Result(result=MANUAL, msg=TVER_MISSING) @@ -5270,13 +5298,11 @@ def fc_ex_model_check(tversion, **kwargs): if (tversion.newer_than("6.0(7a)") and tversion.older_than("6.0(9c)")) or tversion.same_as("6.1(1f)"): fcEntitys = icurl('class', fcEntity_api) fc_nodes = [] - if fcEntitys: - for fcEntity in fcEntitys: - fc_nodes.append(fcEntity['fcEntity']['attributes']['dn'].split('/sys')[0]) + for fcEntity in fcEntitys: + fc_nodes.append(fcEntity['fcEntity']['attributes']['dn'].split('/sys')[0]) if fc_nodes: - fabricNodes = icurl('class', fabricNode_api) - for node in fabricNodes: + for node in fabric_nodes: node_dn = node['fabricNode']['attributes']['dn'] if node_dn in fc_nodes: model = node['fabricNode']['attributes']['model'] @@ -5355,7 +5381,7 @@ def clock_signal_component_failure_check(**kwargs): @check_wrapper(check_title='Stale Decomissioned Spine') -def stale_decomissioned_spine_check(tversion, **kwargs): +def stale_decomissioned_spine_check(tversion, fabric_nodes, **kwargs): result = PASS headers = ["Susceptible Spine Node Id", "Spine Name", "Current Node State"] data = [] @@ -5363,8 +5389,6 @@ def stale_decomissioned_spine_check(tversion, **kwargs): doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#stale-decommissioned-spine' decomissioned_api = 'fabricRsDecommissionNode.json' - active_spine_api = 'topSystem.json' - active_spine_api += '?query-target-filter=eq(topSystem.role,"spine")' if not tversion: return Result(result=MANUAL, msg=TVER_MISSING) @@ -5374,13 +5398,16 @@ def stale_decomissioned_spine_check(tversion, **kwargs): if decomissioned_switches: decommissioned_node_ids = [node['fabricRsDecommissionNode']['attributes']['targetId'] for node in decomissioned_switches] - active_spine_mo = icurl('class', active_spine_api) - for spine in active_spine_mo: - node_id = spine['topSystem']['attributes']['id'] - name = spine['topSystem']['attributes']['name'] - state = spine['topSystem']['attributes']['state'] + for node in fabric_nodes: + if node["fabricNode"]["attributes"]["role"] != "spine": + continue + if node["fabricNode"]["attributes"]["fabricSt"] != "active": + continue + node_id = node["fabricNode"]["attributes"]["id"] + name = node["fabricNode"]["attributes"]["name"] + fabricSt = node["fabricNode"]["attributes"]["fabricSt"] if node_id in decommissioned_node_ids: - data.append([node_id, name, state]) + data.append([node_id, name, fabricSt]) if data: result = FAIL_O return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) @@ -5649,23 +5676,20 @@ def service_bd_forceful_routing_check(cversion, tversion, **kwargs): # Connection Base Check @check_wrapper(check_title='Observer Database Size') -def observer_db_size_check(username, password, **kwargs): +def observer_db_size_check(username, password, fabric_nodes, **kwargs): result = PASS headers = ["Node", "File Location", "Size (GB)"] data = [] recommended_action = 'Contact TAC to analyze and truncate large DB files' doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations#observer-database-size' - topSystem_api = 'topSystem.json' - topSystem_api += '?query-target-filter=eq(topSystem.role,"controller")' - - controllers = icurl('class', topSystem_api) - if not controllers: - return Result(result=ERROR, msg='topSystem response empty. Is the cluster healthy?') + apics = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["role"] == "controller"] + if not apics: + return Result(result=ERROR, msg="No fabricNode of APIC. Is the cluster healthy?", doc_url=doc_url) has_error = False - for apic in controllers: - attr = apic['topSystem']['attributes'] + for apic in apics: + attr = apic['fabricNode']['attributes'] try: c = Connection(attr['address']) c.username = username diff --git a/tests/checks/apic_ssd_check/fabricNode.json b/tests/checks/apic_ssd_check/fabricNode.json new file mode 100644 index 00000000..962a4ad9 --- /dev/null +++ b/tests/checks/apic_ssd_check/fabricNode.json @@ -0,0 +1,93 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] + diff --git a/tests/checks/apic_ssd_check/fabricNode_no_apic.json b/tests/checks/apic_ssd_check/fabricNode_no_apic.json new file mode 100644 index 00000000..254f40d0 --- /dev/null +++ b/tests/checks/apic_ssd_check/fabricNode_no_apic.json @@ -0,0 +1,48 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] + diff --git a/tests/checks/apic_ssd_check/fault_F2731.json b/tests/checks/apic_ssd_check/fault_F2731.json new file mode 100644 index 00000000..578f6827 --- /dev/null +++ b/tests/checks/apic_ssd_check/fault_F2731.json @@ -0,0 +1,12 @@ +[ + { + "faultInst": { + "attributes": { + "code": "F2731", + "changeSet": "mediaWearout (Old: 2, New: 1)", + "description": "Storage unit /dev/sdb on Node 3 mounted at /dev/sdb has 1% life remaining", + "dn": "topology/pod-2/node-3/sys/ch/p-[/dev/sdb]-f-[/dev/sdb]/fault-F2731" + } + } + } +] diff --git a/tests/checks/apic_ssd_check/test_apic_ssd_check.py b/tests/checks/apic_ssd_check/test_apic_ssd_check.py new file mode 100644 index 00000000..c77ff57b --- /dev/null +++ b/tests/checks/apic_ssd_check/test_apic_ssd_check.py @@ -0,0 +1,194 @@ +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__)) + +test_function = "apic_ssd_check" + + +faultInst = 'faultInst.json?query-target-filter=or(eq(faultInst.code,"F2731"),eq(faultInst.code,"F2732"))' + +apic_ips = [ + node["fabricNode"]["attributes"]["address"] + for node in read_data(dir, "fabricNode.json") + if node["fabricNode"]["attributes"]["role"] == "controller" +] + +grep_cmd = 'grep -oE "SSD Wearout Indicator is [0-9]+" /var/log/dme/log/svc_ifc_ae.bin.log | tail -1' +grep_output_hit = "5504||2023-01-11T22:11:26.851446656+00:00||ifc_ae||DBG4||fn=[getWearout]||SSD Wearout Indicator is 4||../svc/ae/src/gen/ifc/beh/imp/./eqpt/StorageBI.cc||395" +grep_output_no_hit = "5504||2023-01-11T22:11:26.851446656+00:00||ifc_ae||DBG4||fn=[getWearout]||SSD Wearout Indicator is 5||../svc/ae/src/gen/ifc/beh/imp/./eqpt/StorageBI.cc||395" + + +@pytest.mark.parametrize( + "icurl_outputs, conn_failure, conn_cmds, cversion, fabric_nodes, expected_result, expected_data", + [ + # New Versions, F273x are effective and raised + ( + {faultInst: read_data(dir, "fault_F2731.json")}, + False, + [], + "4.2(7w)", + read_data(dir, "fabricNode.json"), + script.FAIL_UF, + [["2", "3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], + ), + ( + {faultInst: read_data(dir, "fault_F2731.json")}, + False, + [], + "5.2(1h)", + read_data(dir, "fabricNode.json"), + script.FAIL_UF, + [["2", "3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], + ), + # New Versions, F273x are effective and NOT raised + ( + {faultInst: []}, + False, + [], + "4.2(7w)", + read_data(dir, "fabricNode.json"), + script.PASS, + [], + ), + ( + {faultInst: []}, + False, + [], + "5.2(1h)", + read_data(dir, "fabricNode.json"), + script.PASS, + [], + ), + # Old Versions, but F273x was still raised. + ( + {faultInst: read_data(dir, "fault_F2731.json")}, + False, + [], + "4.2(6o)", + read_data(dir, "fabricNode.json"), + script.FAIL_UF, + [["2", "3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], + ), + + # --- Old Versions, no F273x was raised. --- + + # No fabricNode for APICs + ( + {faultInst: []}, + False, + [], + "4.2(6o)", + read_data(dir, "fabricNode_no_apic.json"), + script.ERROR, + [], + ), + # Exception failure at the very first connection() + ( + {faultInst: []}, + True, + [], + "4.2(6o)", + read_data(dir, "fabricNode.json"), + script.ERROR, + [ + ["1", "1", "-", "-", "Simulated exception at connect()"], + ["1", "2", "-", "-", "Simulated exception at connect()"], + ["2", "3", "-", "-", "Simulated exception at connect()"], + ], + ), + # Exception failure at the grep command + ( + {faultInst: []}, + False, + { + apic_ip: [ + { + "cmd": grep_cmd, + "output": "", + "exception": Exception("Simulated exception at `grep` command"), + } + ] + for apic_ip in apic_ips + }, + "4.2(6o)", + read_data(dir, "fabricNode.json"), + script.ERROR, + [ + ["1", "1", "-", "-", "Simulated exception at `grep` command"], + ["1", "2", "-", "-", "Simulated exception at `grep` command"], + ["2", "3", "-", "-", "Simulated exception at `grep` command"], + ], + ), + # SSD Wearout Indicator is less than 5 + ( + {faultInst: []}, + False, + { + apic_ips[0]: [ + { + "cmd": grep_cmd, + "output": "\n".join([grep_cmd, grep_output_hit]), + "exception": None, + }, + ], + apic_ips[1]: [ + { + "cmd": grep_cmd, + "output": "\n".join([grep_cmd, grep_output_no_hit]), + "exception": None, + }, + ], + apic_ips[2]: [ + { + "cmd": grep_cmd, + "output": "\n".join([grep_cmd, grep_output_hit]), + "exception": None, + }, + ], + }, + "4.2(6o)", + read_data(dir, "fabricNode.json"), + script.FAIL_UF, + [ + ["1", "1", "Solid State Disk", "4", "Contact TAC for replacement"], + ["1", "2", "Solid State Disk", "5", "No Action Required"], + ["2", "3", "Solid State Disk", "4", "Contact TAC for replacement"], + ], + ), + # Pass + ( + {faultInst: []}, + False, + { + apic_ip: [ + { + "cmd": grep_cmd, + "output": "\n".join([grep_cmd, grep_output_no_hit]), + "exception": None, + }, + ] + for apic_ip in apic_ips + }, + "4.2(6o)", + read_data(dir, "fabricNode.json"), + script.PASS, + [], + ), + ], +) +def test_logic(run_check, mock_icurl, mock_conn, cversion, fabric_nodes, expected_result, expected_data): + result = run_check( + cversion=script.AciVersion(cversion), + username="fake_username", + password="fake_password", + fabric_nodes=fabric_nodes, + ) + assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/apic_version_md5_check/fabricNode.json b/tests/checks/apic_version_md5_check/fabricNode.json new file mode 100644 index 00000000..962a4ad9 --- /dev/null +++ b/tests/checks/apic_version_md5_check/fabricNode.json @@ -0,0 +1,93 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] + diff --git a/tests/checks/apic_version_md5_check/fabricNode_no_apic.json b/tests/checks/apic_version_md5_check/fabricNode_no_apic.json new file mode 100644 index 00000000..254f40d0 --- /dev/null +++ b/tests/checks/apic_version_md5_check/fabricNode_no_apic.json @@ -0,0 +1,48 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] + diff --git a/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py b/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py index 7b3e2470..f920ce0f 100644 --- a/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py +++ b/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py @@ -11,14 +11,13 @@ test_function = "apic_version_md5_check" + api_firmware = "fwrepo/fw-aci-apic-dk9.6.0.5h.json" -api_topSystem = "topSystem.json" -topSystems = read_data(dir, "topSystem.json") apic_ips = [ - mo["topSystem"]["attributes"]["address"] - for mo in topSystems["imdata"] - if mo["topSystem"]["attributes"]["role"] == "controller" + node["fabricNode"]["attributes"]["address"] + for node in read_data(dir, "fabricNode.json") + if node["fabricNode"]["attributes"]["role"] == "controller" ] ls_cmd = "ls -aslh /firmware/fwrepos/fwrepo/aci-apic-dk9.6.0.5h.bin" @@ -51,38 +50,47 @@ @pytest.mark.parametrize( - "icurl_outputs, conn_failure, conn_cmds, tversion, expected_result", + "icurl_outputs, conn_failure, conn_cmds, tversion, fabric_nodes, expected_result, expected_data", [ # tversion missing - ({}, False, [], None, script.MANUAL), + ({}, False, [], None, read_data(dir, "fabricNode.json"), script.MANUAL, []), # Image signing failure shown in firmwareFirmware ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h_image_sign_fail.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h_image_sign_fail.json")}, False, [], "6.0(5h)", + read_data(dir, "fabricNode.json"), script.FAIL_UF, + [["All", "6.0(5h)", "d5afca58fce2018495d068c000000000", "Target image is corrupted"]], + ), + # No fabricNode for APICs + ( + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, + False, + [], + "6.0(5h)", + read_data(dir, "fabricNode_no_apic.json"), + script.ERROR, + [], ), # Exception failure at the very first connection() ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, True, [], "6.0(5h)", + read_data(dir, "fabricNode.json"), script.ERROR, + [ + ["apic1", "-", "-", "Simulated exception at connect()"], + ["apic2", "-", "-", "Simulated exception at connect()"], + ["apic3", "-", "-", "Simulated exception at connect()"], + ], ), # Exception failure at the ls command ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, False, { apic_ip: [ @@ -95,14 +103,17 @@ for apic_ip in apic_ips }, "6.0(5h)", + read_data(dir, "fabricNode.json"), script.ERROR, + [ + ["apic1", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], + ["apic2", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], + ["apic3", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], + ], ), # No such file output from the ls command ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, False, { apic_ip: [ @@ -115,14 +126,17 @@ for apic_ip in apic_ips }, "6.0(5h)", + read_data(dir, "fabricNode.json"), script.FAIL_UF, + [ + ["apic1", "6.0(5h)", "-", "image not found"], + ["apic2", "6.0(5h)", "-", "image not found"], + ["apic3", "6.0(5h)", "-", "image not found"], + ], ), # Exception failure at the cat command ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, False, { apic_ip: [ @@ -140,14 +154,17 @@ for apic_ip in apic_ips }, "6.0(5h)", + read_data(dir, "fabricNode.json"), script.ERROR, + [ + ["apic1", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], + ["apic2", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], + ["apic3", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], + ], ), # No such file output from the cat command ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, False, { apic_ip: [ @@ -165,14 +182,17 @@ for apic_ip in apic_ips }, "6.0(5h)", + read_data(dir, "fabricNode.json"), script.FAIL_UF, + [ + ["apic1", "6.0(5h)", "-", "md5sum file not found"], + ["apic2", "6.0(5h)", "-", "md5sum file not found"], + ["apic3", "6.0(5h)", "-", "md5sum file not found"], + ], ), # Unexpected output from the cat command ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, False, { apic_ip: [ @@ -190,14 +210,17 @@ for apic_ip in apic_ips }, "6.0(5h)", + read_data(dir, "fabricNode.json"), script.ERROR, + [ + ["apic1", "6.0(5h)", "-", "unexpected output when checking md5sum file"], + ["apic2", "6.0(5h)", "-", "unexpected output when checking md5sum file"], + ["apic3", "6.0(5h)", "-", "unexpected output when checking md5sum file"], + ], ), # Failure because md5sum on each APIC do not match ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, False, { apic_ips[0]: [ @@ -238,14 +261,17 @@ ], }, "6.0(5h)", + read_data(dir, "fabricNode.json"), script.FAIL_UF, + [ + ["apic1", "6.0(5h)", "d5afca58fce2018495d068c44eb4a547", "md5sum do not match on all APICs"], + ["apic2", "6.0(5h)", "d5afca58fce2018495d068c000000000", "md5sum do not match on all APICs"], + ["apic3", "6.0(5h)", "d5afca58fce2018495d068c44eb4a547", "md5sum do not match on all APICs"], + ], ), # Pass ( - { - api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), - api_topSystem: read_data(dir, "topSystem.json"), - }, + {api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json")}, False, { apic_ip: [ @@ -263,14 +289,18 @@ for apic_ip in apic_ips }, "6.0(5h)", + read_data(dir, "fabricNode.json"), script.PASS, + [], ), ], ) -def test_logic(run_check, mock_icurl, mock_conn, tversion, expected_result): +def test_logic(run_check, mock_icurl, mock_conn, tversion, fabric_nodes, expected_result, expected_data): result = run_check( tversion=script.AciVersion(tversion) if tversion else None, username="fake_username", password="fake_password", + fabric_nodes=fabric_nodes, ) assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/apic_version_md5_check/topSystem.json b/tests/checks/apic_version_md5_check/topSystem.json deleted file mode 100644 index 65dca386..00000000 --- a/tests/checks/apic_version_md5_check/topSystem.json +++ /dev/null @@ -1,1023 +0,0 @@ -{ - "totalCount": "17", - "imdata": [ - { - "topSystem": { - "attributes": { - "address": "10.2.0.1", - "bootstrapState": "none", - "childAction": "", - "clusterTimeDiff": "0", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:11:04.529-07:00", - "dn": "topology/pod-1/node-1/sys", - "enforceSubnetCheck": "no", - "etepAddr": "0.0.0.0", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "1", - "inbMgmtAddr": "192.168.12.1", - "inbMgmtAddr6": "fc00::1", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-26T22:37:38.265-07:00", - "lastResetReason": "unknown", - "lcOwn": "local", - "modTs": "2025-03-26T23:17:06.831-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-APIC-1", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "192.168.100.12", - "oobMgmtAddr6": "fe80::86b2:61ff:fe91:932e", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "22", - "oobMgmtGateway": "192.168.100.1", - "oobMgmtGateway6": "fc00::ffff", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "0", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "controller", - "serial": "ABC1234DEFG", - "serverType": "unspecified", - "siteId": "0", - "state": "in-service", - "status": "", - "systemUpTime": "09:13:33:26.000", - "tepPool": "0.0.0.0", - "unicastXrEpLearnDisable": "no", - "version": "6.1(2.145a)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.0.2", - "bootstrapState": "none", - "childAction": "", - "clusterTimeDiff": "108403", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:11:09.229-07:00", - "dn": "topology/pod-1/node-2/sys", - "enforceSubnetCheck": "no", - "etepAddr": "0.0.0.0", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "2", - "inbMgmtAddr": "192.168.12.2", - "inbMgmtAddr6": "fc00::1", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-27T20:07:42.002-07:00", - "lastResetReason": "unknown", - "lcOwn": "local", - "modTs": "2025-03-27T20:37:50.245-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-APIC-2", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "192.168.100.13", - "oobMgmtAddr6": "fe80::86b2:61ff:fe70:9b3e", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "22", - "oobMgmtGateway": "192.168.100.1", - "oobMgmtGateway6": "fc00::ffff", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "0", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "controller", - "serial": "ABC1235DEFG", - "serverType": "unspecified", - "siteId": "0", - "state": "in-service", - "status": "", - "systemUpTime": "08:16:03:27.000", - "tepPool": "0.0.0.0", - "unicastXrEpLearnDisable": "no", - "version": "6.1(2.145a)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.160.66", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "109356", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:11:08.219-07:00", - "dn": "topology/pod-1/node-101/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "101", - "inbMgmtAddr": "192.168.12.101", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "0.0.0.0", - "lastRebootTime": "2025-04-04T12:03:47.240-07:00", - "lastResetReason": "unknown", - "lcOwn": "local", - "modTs": "2025-04-04T12:53:13.476-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Leaf-101", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlOperPodId": "1", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "leaf", - "serial": "ABC1236DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "01:00:07:21.000", - "tepPool": "10.2.0.0/16", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-15.3(1d)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.0.3", - "bootstrapState": "none", - "childAction": "", - "clusterTimeDiff": "166085", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:10:03.540-07:00", - "dn": "topology/pod-1/node-3/sys", - "enforceSubnetCheck": "no", - "etepAddr": "0.0.0.0", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "3", - "inbMgmtAddr": "192.168.12.3", - "inbMgmtAddr6": "fc00::1", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-19T23:01:21.686-07:00", - "lastResetReason": "unknown", - "lcOwn": "local", - "modTs": "2025-03-19T23:06:46.170-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-APIC-3", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "192.168.100.14", - "oobMgmtAddr6": "fe80::86b2:61ff:fe70:7dde", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "22", - "oobMgmtGateway": "192.168.100.1", - "oobMgmtGateway6": "fc00::ffff", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "0", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "controller", - "serial": "ABC1237DEFG", - "serverType": "unspecified", - "siteId": "0", - "state": "in-service", - "status": "", - "systemUpTime": "16:13:11:28.000", - "tepPool": "0.0.0.0", - "unicastXrEpLearnDisable": "no", - "version": "6.1(2.145a)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.160.96", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "167238", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:10:10.361-07:00", - "dn": "topology/pod-1/node-103/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "103", - "inbMgmtAddr": "192.168.12.103", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:36:23.357-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-27T19:21:12.187-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Leaf-103", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "1", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "leaf", - "serial": "ABC1238DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:33:47.000", - "tepPool": "10.2.0.0/16", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.160.64", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "155713", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:10:21.887-07:00", - "dn": "topology/pod-1/node-104/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "104", - "inbMgmtAddr": "192.168.12.104", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T01:04:51.864-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-27T19:59:43.390-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Leaf-104", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "1", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "leaf", - "serial": "ABC1239DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:05:30.000", - "tepPool": "10.2.0.0/16", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.160.99", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "41285", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:12:16.321-07:00", - "dn": "topology/pod-1/node-106/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "106", - "inbMgmtAddr": "192.168.12.106", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:40:00.676-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T00:52:58.843-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Leaf-106", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "1", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "leaf", - "serial": "ABC1240DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:32:15.000", - "tepPool": "10.2.0.0/16", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "192.168.221.45", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "160428", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:10:17.178-07:00", - "dn": "topology/pod-1/node-213/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "213", - "inbMgmtAddr": "0.0.0.0", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "0", - "inbMgmtGateway": "0.0.0.0", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:36:57.724-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T00:50:56.201-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "RL-213", - "nameAlias": "", - "nodeType": "remote-leaf-wan", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "yes", - "rlAutoMode": "yes", - "rlGroupId": "1", - "rlOperPodId": "1", - "rlRoutableMode": "yes", - "rldirectMode": "yes", - "role": "leaf", - "serial": "ABC1241DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:33:19.000", - "tepPool": "192.168.221.0/25", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.3.96.64", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "-46384", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:13:43.988-07:00", - "dn": "topology/pod-2/node-202/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.202.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "202", - "inbMgmtAddr": "192.168.12.212", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:39:48.586-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-26T13:08:21.651-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Leaf-202", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "2", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "2", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "leaf", - "serial": "ABC1242DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:33:55.000", - "tepPool": "10.3.0.0/16", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "192.168.221.89", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "1265729", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T11:51:51.876-07:00", - "dn": "topology/pod-1/node-212/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "212", - "inbMgmtAddr": "0.0.0.0", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "0", - "inbMgmtGateway": "0.0.0.0", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:18:05.141-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T00:49:25.483-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "RL-212", - "nameAlias": "", - "nodeType": "remote-leaf-wan", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "yes", - "rlAutoMode": "yes", - "rlGroupId": "1", - "rlOperPodId": "1", - "rlRoutableMode": "yes", - "rldirectMode": "yes", - "role": "leaf", - "serial": "ABC1243DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:33:47.000", - "tepPool": "192.168.221.0/25", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.160.65", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "94487", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:11:23.132-07:00", - "dn": "topology/pod-1/node-1001/sys", - "enforceSubnetCheck": "no", - "etepAddr": "0.0.0.0", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "1001", - "inbMgmtAddr": "192.168.12.201", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "32", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:38:20.689-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T00:52:16.469-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Spine-1001", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "1", - "rlRoutableMode": "yes", - "rldirectMode": "yes", - "role": "spine", - "serial": "ABC1244DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:33:03.000", - "tepPool": "10.2.0.0/16", - "unicastXrEpLearnDisable": "no", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "192.168.221.87", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "518949", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:04:18.655-07:00", - "dn": "topology/pod-1/node-214/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "214", - "inbMgmtAddr": "0.0.0.0", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "0", - "inbMgmtGateway": "0.0.0.0", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:31:20.546-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T00:52:15.931-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "RL-214", - "nameAlias": "", - "nodeType": "remote-leaf-wan", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "yes", - "rlAutoMode": "yes", - "rlGroupId": "1", - "rlOperPodId": "1", - "rlRoutableMode": "yes", - "rldirectMode": "yes", - "role": "leaf", - "serial": "ABC1245DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:32:58.000", - "tepPool": "192.168.221.0/25", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.3.32.65", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "393685", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:06:23.918-07:00", - "dn": "topology/pod-2/node-201/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.202.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "201", - "inbMgmtAddr": "192.168.12.211", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T01:01:52.297-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T01:22:03.406-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Leaf-201", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "2", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "2", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "leaf", - "serial": "ABC1246DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:04:31.000", - "tepPool": "10.3.0.0/16", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.160.97", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "67158", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:11:50.428-07:00", - "dn": "topology/pod-1/node-1002/sys", - "enforceSubnetCheck": "no", - "etepAddr": "0.0.0.0", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "1002", - "inbMgmtAddr": "192.168.12.202", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "32", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-11T12:54:41.632-07:00", - "lastResetReason": "reload", - "lcOwn": "local", - "modTs": "2025-03-11T13:12:33.668-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Spine-1002", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "1", - "rlRoutableMode": "yes", - "rldirectMode": "yes", - "role": "spine", - "serial": "ABC1247DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "24:23:17:08.000", - "tepPool": "10.2.0.0/16", - "unicastXrEpLearnDisable": "no", - "version": "n9000-16.1(2.145)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.3.32.64", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "532782", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:04:04.823-07:00", - "dn": "topology/pod-2/node-2001/sys", - "enforceSubnetCheck": "no", - "etepAddr": "0.0.0.0", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "2001", - "inbMgmtAddr": "0.0.0.0", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "0", - "inbMgmtGateway": "0.0.0.0", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:30:53.399-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T00:50:33.631-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Spine-2001", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "2", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "2", - "rlRoutableMode": "yes", - "rldirectMode": "yes", - "role": "spine", - "serial": "ABC1248DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:33:11.000", - "tepPool": "10.3.0.0/16", - "unicastXrEpLearnDisable": "no", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.2.160.98", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "179134", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:09:58.511-07:00", - "dn": "topology/pod-1/node-102/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "102", - "inbMgmtAddr": "192.168.12.102", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "24", - "inbMgmtGateway": "192.168.12.254", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:36:18.789-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T00:51:25.528-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "S2-Leaf-102", - "nameAlias": "", - "nodeType": "unspecified", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "no", - "rlAutoMode": "no", - "rlGroupId": "0", - "rlOperPodId": "1", - "rlRoutableMode": "no", - "rldirectMode": "no", - "role": "leaf", - "serial": "ABC1249DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:33:40.000", - "tepPool": "10.2.0.0/16", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "192.168.221.91", - "bootstrapState": "done", - "childAction": "", - "clusterTimeDiff": "-529288", - "configIssues": "", - "controlPlaneMTU": "1400", - "currentTime": "2025-04-05T12:21:46.934-07:00", - "dn": "topology/pod-1/node-211/sys", - "enforceSubnetCheck": "no", - "etepAddr": "192.168.201.1", - "fabricDomain": "S2-Fabric", - "fabricId": "1", - "fabricMAC": "00:22:BD:F8:19:FF", - "id": "211", - "inbMgmtAddr": "0.0.0.0", - "inbMgmtAddr6": "::", - "inbMgmtAddr6Mask": "0", - "inbMgmtAddrMask": "0", - "inbMgmtGateway": "0.0.0.0", - "inbMgmtGateway6": "::", - "lastRebootTime": "2025-03-20T00:47:47.935-07:00", - "lastResetReason": "installer", - "lcOwn": "local", - "modTs": "2025-03-20T00:52:13.287-07:00", - "mode": "unspecified", - "monPolDn": "uni/fabric/monfab-default", - "name": "RL-211", - "nameAlias": "", - "nodeType": "remote-leaf-wan", - "oobMgmtAddr": "0.0.0.0", - "oobMgmtAddr6": "::", - "oobMgmtAddr6Mask": "0", - "oobMgmtAddrMask": "0", - "oobMgmtGateway": "0.0.0.0", - "oobMgmtGateway6": "::", - "podId": "1", - "remoteNetworkId": "0", - "remoteNode": "yes", - "rlAutoMode": "yes", - "rlGroupId": "1", - "rlOperPodId": "1", - "rlRoutableMode": "yes", - "rldirectMode": "yes", - "role": "leaf", - "serial": "ABC1250DEFG", - "serverType": "unspecified", - "siteId": "2", - "state": "in-service", - "status": "", - "systemUpTime": "16:11:33:59.000", - "tepPool": "192.168.221.0/25", - "unicastXrEpLearnDisable": "yes", - "version": "n9000-16.1(2.158)", - "virtualMode": "no" - } - } - } - ] -} diff --git a/tests/checks/fabric_link_redundancy_check/fabricNode.json b/tests/checks/fabric_link_redundancy_check/fabricNode.json index 7bb924e2..3dffa268 100644 --- a/tests/checks/fabric_link_redundancy_check/fabricNode.json +++ b/tests/checks/fabric_link_redundancy_check/fabricNode.json @@ -1,101 +1,122 @@ [ - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-101", - "id": "101", - "name": "LF101", - "role": "leaf", - "nodeType": "unspecified" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-102", - "id": "102", - "name": "LF102", - "role": "leaf", - "nodeType": "unspecified" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-103", - "id": "103", - "name": "LF103", - "role": "leaf", - "nodeType": "unspecified" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-111", - "id": "111", - "name": "T2_LF111", - "role": "leaf", - "nodeType": "tier-2-leaf" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-112", - "id": "112", - "name": "T2_LF112", - "role": "leaf", - "nodeType": "tier-2-leaf" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-121", - "id": "121", - "name": "RL_LF121", - "role": "leaf", - "nodeType": "remote-leaf-wan" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-122", - "id": "122", - "name": "RL_LF122", - "role": "leaf", - "nodeType": "remote-leaf-wan" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-1001", - "id": "1001", - "name": "SP1001", - "role": "spine", - "nodeType": "unspecified" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-1002", - "id": "1002", - "name": "SP1002", - "role": "spine", - "nodeType": "unspecified" - } - } + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "name": "apic1", + "role": "controller", + "nodeType": "unspecified" + } } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "name": "LF101", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "name": "LF102", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-103", + "fabricSt": "active", + "id": "103", + "name": "LF103", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-111", + "fabricSt": "active", + "id": "111", + "name": "T2_LF111", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-112", + "fabricSt": "active", + "id": "112", + "name": "T2_LF112", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-121", + "fabricSt": "active", + "id": "121", + "name": "RL_LF121", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-122", + "fabricSt": "active", + "id": "122", + "name": "RL_LF122", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1001", + "fabricSt": "active", + "id": "1001", + "name": "SP1001", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1002", + "fabricSt": "active", + "id": "1002", + "name": "SP1002", + "role": "spine", + "nodeType": "unspecified" + } + } + } ] diff --git a/tests/checks/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py b/tests/checks/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py index 2716d620..b31f8fe5 100644 --- a/tests/checks/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py +++ b/tests/checks/fabric_link_redundancy_check/test_fabric_link_redundancy_check.py @@ -11,52 +11,57 @@ test_function = "fabric_link_redundancy_check" -fabric_nodes_api = 'fabricNode.json' -fabric_nodes_api += '?query-target-filter=and(or(eq(fabricNode.role,"leaf"),eq(fabricNode.role,"spine")),eq(fabricNode.fabricSt,"active"))' - # icurl queries -lldp_adj_api = 'lldpAdjEp.json' +lldp_adj_api = "lldpAdjEp.json" lldp_adj_api += '?query-target-filter=wcard(lldpAdjEp.sysDesc,"topology/pod")' @pytest.mark.parametrize( - "icurl_outputs, expected_result", + "icurl_outputs, fabric_nodes, expected_result, expected_data", [ # FAILING = T1 leaf101 single-homed, T1 leaf102 none, T1 leaf103 multi-homed ( - { - fabric_nodes_api: read_data(dir, "fabricNode.json"), - lldp_adj_api: read_data(dir, "lldpAdjEp_pos_spine_only.json"), - }, + {lldp_adj_api: read_data(dir, "lldpAdjEp_pos_spine_only.json")}, + read_data(dir, "fabricNode.json"), script.FAIL_O, + [ + ["LF101", "SP1001", "Only one spine adjacency"], + ["LF102", "", "No spine adjacency"], + ] ), # FAILING = T1 leafs multi-homed, T2 leaf111 single-homed, T2 leaf112 multi-homed ( - { - fabric_nodes_api: read_data(dir, "fabricNode.json"), - lldp_adj_api: read_data(dir, "lldpAdjEp_pos_t1_only.json"), - }, + {lldp_adj_api: read_data(dir, "lldpAdjEp_pos_t1_only.json")}, + read_data(dir, "fabricNode.json"), script.FAIL_O, + [ + ["T2_LF111", "LF102", "Only one tier 1 leaf adjacency"], + ] ), # FAILING = T1 leaf101 single-homed, T1 leaf102 none, T1 leaf103 multi-homed # T2 leaf111 single-homed, T2 leaf112 multi-homed ( - { - fabric_nodes_api: read_data(dir, "fabricNode.json"), - lldp_adj_api: read_data(dir, "lldpAdjEp_pos_spine_t1.json"), - }, + {lldp_adj_api: read_data(dir, "lldpAdjEp_pos_spine_t1.json")}, + read_data(dir, "fabricNode.json"), script.FAIL_O, + [ + ["LF101", "SP1001", "Only one spine adjacency"], + ["LF102", "", "No spine adjacency"], + ["T2_LF111", "LF102", "Only one tier 1 leaf adjacency"], + ] ), # PASSING = ALL LEAF SWITCHES ARE MULTI-HOMED except for RL ( - { - fabric_nodes_api: read_data(dir, "fabricNode.json"), - lldp_adj_api: read_data(dir, "lldpAdjEp_neg.json"), - }, + {lldp_adj_api: read_data(dir, "lldpAdjEp_neg.json")}, + read_data(dir, "fabricNode.json"), script.PASS, + [], ), ], ) -def test_logic(run_check, mock_icurl , expected_result): - result = run_check() +def test_logic(run_check, mock_icurl, fabric_nodes, expected_result, expected_data): + result = run_check( + fabric_nodes=fabric_nodes, + ) assert result.result == expected_result + assert sorted(result.data) == sorted(expected_data) diff --git a/tests/checks/fabricdomain_name_check/fabricNode.json b/tests/checks/fabricdomain_name_check/fabricNode.json new file mode 100644 index 00000000..80f5c867 --- /dev/null +++ b/tests/checks/fabricdomain_name_check/fabricNode.json @@ -0,0 +1,108 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93108TC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.103", + "dn": "topology/pod-1/node-103", + "fabricSt": "active", + "id": "103", + "model": "N9K-C93180YC-FX3", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf103", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] + diff --git a/tests/checks/fabricdomain_name_check/fabricNode_no_apic1.json b/tests/checks/fabricdomain_name_check/fabricNode_no_apic1.json new file mode 100644 index 00000000..fcc76406 --- /dev/null +++ b/tests/checks/fabricdomain_name_check/fabricNode_no_apic1.json @@ -0,0 +1,93 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93108TC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.103", + "dn": "topology/pod-1/node-103", + "fabricSt": "active", + "id": "103", + "model": "N9K-C93180YC-FX3", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf103", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] + diff --git a/tests/checks/fabricdomain_name_check/test_fabricdomain_name_check.py b/tests/checks/fabricdomain_name_check/test_fabricdomain_name_check.py index 0ec2d6b0..c2881bba 100644 --- a/tests/checks/fabricdomain_name_check/test_fabricdomain_name_check.py +++ b/tests/checks/fabricdomain_name_check/test_fabricdomain_name_check.py @@ -12,70 +12,115 @@ test_function = "fabricdomain_name_check" # icurl queries -topSystem = 'topSystem.json?query-target-filter=eq(topSystem.role,"controller")' +topSystem = 'topology/pod-1/node-1/sys.json' @pytest.mark.parametrize( - "icurl_outputs, cversion, tversion, expected_result", + "icurl_outputs, cversion, tversion, fabric_nodes, expected_result, expected_data", [ - # # char test + # tversion missing + ( + {topSystem: read_data(dir, "topSystem_1POS.json")}, + "5.2(3g)", + None, + read_data(dir, "fabricNode.json"), + script.MANUAL, + [], + ), + # APIC 1 missing in fabric_nodes (fabricNode) + ( + {topSystem: read_data(dir, "topSystem_1POS.json")}, + "5.2(3g)", + "6.0(2h)", + read_data(dir, "fabricNode_no_apic1.json"), + script.ERROR, + [], + ), + # APIC 1 missing in topSystem + ( + {topSystem: []}, + "5.2(3g)", + "6.0(2h)", + read_data(dir, "fabricNode.json"), + script.ERROR, + [], + ), + # `;` char test ( {topSystem: read_data(dir, "topSystem_1POS.json")}, "5.2(3g)", "6.0(2h)", + read_data(dir, "fabricNode.json"), script.FAIL_O, + [["fabric;4", "Contains a special character"]] ), ( {topSystem: read_data(dir, "topSystem_1POS.json")}, "6.0(3a)", "6.0(2h)", + read_data(dir, "fabricNode.json"), script.FAIL_O, + [["fabric;4", "Contains a special character"]] ), - # ; char test + # `#` char test ( {topSystem: read_data(dir, "topSystem_2POS.json")}, "5.2(3g)", "6.0(2h)", + read_data(dir, "fabricNode.json"), script.FAIL_O, + [["fabric#4", "Contains a special character"]] ), ( {topSystem: read_data(dir, "topSystem_2POS.json")}, "6.0(3a)", "6.0(2h)", + read_data(dir, "fabricNode.json"), script.FAIL_O, + [["fabric#4", "Contains a special character"]] ), # Neither ; or # in fabricDomain ( {topSystem: read_data(dir, "topSystem_NEG.json")}, "5.2(3g)", "6.0(2h)", + read_data(dir, "fabricNode.json"), script.PASS, + [], ), # only affected 6.0(2h), regardless of special chars ( {topSystem: read_data(dir, "topSystem_1POS.json")}, "5.2(3g)", "6.0(1j)", + read_data(dir, "fabricNode.json"), script.PASS, + [], ), # Eventual 6.0(3) has fix ( {topSystem: read_data(dir, "topSystem_1POS.json")}, "5.2(3g)", "6.0(3a)", + read_data(dir, "fabricNode.json"), script.PASS, + [], ), ( {topSystem: read_data(dir, "topSystem_1POS.json")}, "6.0(3a)", "6.0(4a)", + read_data(dir, "fabricNode.json"), script.PASS, + [], ), ], ) -def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): +def test_logic(run_check, mock_icurl, cversion, tversion, fabric_nodes, expected_result, expected_data): result = run_check( cversion=script.AciVersion(cversion), - tversion=script.AciVersion(tversion), + tversion=script.AciVersion(tversion) if tversion else None, + fabric_nodes=fabric_nodes, ) assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/fabricdomain_name_check/topSystem_1POS.json b/tests/checks/fabricdomain_name_check/topSystem_1POS.json index e0764f06..3bd73dfb 100644 --- a/tests/checks/fabricdomain_name_check/topSystem_1POS.json +++ b/tests/checks/fabricdomain_name_check/topSystem_1POS.json @@ -9,27 +9,5 @@ "role": "controller" } } - }, - { - "topSystem": { - "attributes": { - "address": "10.0.0.2", - "fabricId": "1", - "id": "2", - "fabricDomain": "fabric;4", - "role": "controller" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.0.0.3", - "fabricId": "1", - "id": "3", - "fabricDomain": "fabric;4", - "role": "controller" - } - } } ] diff --git a/tests/checks/fabricdomain_name_check/topSystem_2POS.json b/tests/checks/fabricdomain_name_check/topSystem_2POS.json index e353b36a..175bdd80 100644 --- a/tests/checks/fabricdomain_name_check/topSystem_2POS.json +++ b/tests/checks/fabricdomain_name_check/topSystem_2POS.json @@ -9,27 +9,5 @@ "role": "controller" } } - }, - { - "topSystem": { - "attributes": { - "address": "10.0.0.2", - "fabricId": "1", - "id": "2", - "fabricDomain": "fabric#4", - "role": "controller" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.0.0.3", - "fabricId": "1", - "id": "3", - "fabricDomain": "fabric#4", - "role": "controller" - } - } } ] diff --git a/tests/checks/fabricdomain_name_check/topSystem_NEG.json b/tests/checks/fabricdomain_name_check/topSystem_NEG.json index 7a1c1e73..8e6d3ba6 100644 --- a/tests/checks/fabricdomain_name_check/topSystem_NEG.json +++ b/tests/checks/fabricdomain_name_check/topSystem_NEG.json @@ -9,27 +9,5 @@ "role": "controller" } } - }, - { - "topSystem": { - "attributes": { - "address": "10.0.0.2", - "fabricId": "1", - "id": "2", - "fabricDomain": "fabric4", - "role": "controller" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.0.0.3", - "fabricId": "1", - "id": "3", - "fabricDomain": "fabric4", - "role": "controller" - } - } } ] diff --git a/tests/checks/fc_ex_model_check/fabricNode_NEG.json b/tests/checks/fc_ex_model_check/fabricNode_NEG.json index 0637a088..9b11d6b4 100644 --- a/tests/checks/fc_ex_model_check/fabricNode_NEG.json +++ b/tests/checks/fc_ex_model_check/fabricNode_NEG.json @@ -1 +1,107 @@ -[] \ No newline at end of file +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93108TC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.103", + "dn": "topology/pod-1/node-103", + "fabricSt": "active", + "id": "103", + "model": "N9K-C93180YC-FX3", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf103", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] diff --git a/tests/checks/fc_ex_model_check/fabricNode_POS.json b/tests/checks/fc_ex_model_check/fabricNode_POS.json index 0d12e7ce..d0b31928 100644 --- a/tests/checks/fc_ex_model_check/fabricNode_POS.json +++ b/tests/checks/fc_ex_model_check/fabricNode_POS.json @@ -1,26 +1,122 @@ [ - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-101", - "model": "N9K-C93180YC-EX" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-102", - "model": "N9K-C93108TC-EX" - } - } - }, - { - "fabricNode": { - "attributes": { - "dn": "topology/pod-1/node-103", - "model": "N9K-C93108LC-EX" - } - } - } -] \ No newline at end of file + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-EX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93108TC-EX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.103", + "dn": "topology/pod-1/node-103", + "fabricSt": "active", + "id": "103", + "model": "N9K-C93108LC-EX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf103", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.104", + "dn": "topology/pod-1/node-104", + "fabricSt": "active", + "id": "104", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf104", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] diff --git a/tests/checks/fc_ex_model_check/fcEntity_101_102.json b/tests/checks/fc_ex_model_check/fcEntity_101_102.json new file mode 100644 index 00000000..8f052ce3 --- /dev/null +++ b/tests/checks/fc_ex_model_check/fcEntity_101_102.json @@ -0,0 +1,18 @@ +[ + { + "fcEntity": { + "attributes": { + "adminSt": "enabled", + "dn": "topology/pod-1/node-102/sys/fc" + } + } + }, + { + "fcEntity": { + "attributes": { + "adminSt": "enabled", + "dn": "topology/pod-1/node-101/sys/fc" + } + } + } +] diff --git a/tests/checks/fc_ex_model_check/fcEntity_101_102_103.json b/tests/checks/fc_ex_model_check/fcEntity_101_102_103.json new file mode 100644 index 00000000..9da46636 --- /dev/null +++ b/tests/checks/fc_ex_model_check/fcEntity_101_102_103.json @@ -0,0 +1,26 @@ +[ + { + "fcEntity": { + "attributes": { + "adminSt": "enabled", + "dn": "topology/pod-1/node-102/sys/fc" + } + } + }, + { + "fcEntity": { + "attributes": { + "adminSt": "enabled", + "dn": "topology/pod-1/node-101/sys/fc" + } + } + }, + { + "fcEntity": { + "attributes": { + "adminSt": "enabled", + "dn": "topology/pod-1/node-103/sys/fc" + } + } + } +] diff --git a/tests/checks/fc_ex_model_check/fcEntity_104.json b/tests/checks/fc_ex_model_check/fcEntity_104.json new file mode 100644 index 00000000..eed71689 --- /dev/null +++ b/tests/checks/fc_ex_model_check/fcEntity_104.json @@ -0,0 +1,10 @@ +[ + { + "fcEntity": { + "attributes": { + "adminSt": "enabled", + "dn": "topology/pod-1/node-104/sys/fc" + } + } + } +] diff --git a/tests/checks/fc_ex_model_check/fcEntity_NEG.json b/tests/checks/fc_ex_model_check/fcEntity_NEG.json deleted file mode 100644 index 0637a088..00000000 --- a/tests/checks/fc_ex_model_check/fcEntity_NEG.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/tests/checks/fc_ex_model_check/fcEntity_POS.json b/tests/checks/fc_ex_model_check/fcEntity_POS.json deleted file mode 100644 index 162a77cc..00000000 --- a/tests/checks/fc_ex_model_check/fcEntity_POS.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "fcEntity": { - "attributes": { - "adminSt": "enabled", - "dn": "topology/pod-1/node-102/sys/fc" - } - } - }, - { - "fcEntity": { - "attributes": { - "adminSt": "enabled", - "dn": "topology/pod-1/node-101/sys/fc" - } - } - } -] diff --git a/tests/checks/fc_ex_model_check/test_fc_ex_model_check.py b/tests/checks/fc_ex_model_check/test_fc_ex_model_check.py index 82b59e52..88e421c2 100644 --- a/tests/checks/fc_ex_model_check/test_fc_ex_model_check.py +++ b/tests/checks/fc_ex_model_check/test_fc_ex_model_check.py @@ -12,56 +12,129 @@ test_function = "fc_ex_model_check" # icurl queries - -fcEntity_api = 'fcEntity.json' -fabricNode_api = 'fabricNode.json' -fabricNode_api += '?query-target-filter=wcard(fabricNode.model,".*EX")' +fcEntity_api = "fcEntity.json" @pytest.mark.parametrize( - "icurl_outputs, tversion, expected_result", + "icurl_outputs, tversion, fabric_nodes, expected_result, expected_data", [ - ## FABRIC HAS EX NODES and FC/FCOE CONFIG + # TVERSION MISSING + ( + {fcEntity_api: read_data(dir, "fcEntity_101_102_103.json")}, + None, + read_data(dir, "fabricNode_POS.json"), + script.MANUAL, + [], + ), + # TVERSION NOT AFFECTED + ( + {fcEntity_api: read_data(dir, "fcEntity_101_102_103.json")}, + "6.0(1f)", + read_data(dir, "fabricNode_POS.json"), + script.PASS, + [], + ), ( - {fcEntity_api: read_data(dir, "fcEntity_POS.json"), - fabricNode_api: read_data(dir, "fabricNode_POS.json")}, + {fcEntity_api: read_data(dir, "fcEntity_101_102_103.json")}, + "6.0(9f)", + read_data(dir, "fabricNode_POS.json"), + script.PASS, + [], + ), + ( + {fcEntity_api: read_data(dir, "fcEntity_101_102_103.json")}, + "6.1(4h)", + read_data(dir, "fabricNode_POS.json"), + script.PASS, + [], + ), + # FABRIC HAS EX NODES and ALL OF THEM HAVE FC/FCOE CONFIG + ( + {fcEntity_api: read_data(dir, "fcEntity_101_102_103.json")}, "6.1(1f)", + read_data(dir, "fabricNode_POS.json"), script.FAIL_O, + [ + ["topology/pod-1/node-101", "N9K-C93180YC-EX"], + ["topology/pod-1/node-102", "N9K-C93108TC-EX"], + ["topology/pod-1/node-103", "N9K-C93108LC-EX"], + ], ), ( - {fcEntity_api: read_data(dir, "fcEntity_POS.json"), - fabricNode_api: read_data(dir, "fabricNode_POS.json")}, + {fcEntity_api: read_data(dir, "fcEntity_101_102_103.json")}, "6.0(7e)", + read_data(dir, "fabricNode_POS.json"), script.FAIL_O, + [ + ["topology/pod-1/node-101", "N9K-C93180YC-EX"], + ["topology/pod-1/node-102", "N9K-C93108TC-EX"], + ["topology/pod-1/node-103", "N9K-C93108LC-EX"], + ], ), - # TVERSION NOT AFFECTED + # FABRIC HAS EX NODES and SOME OF THEM HAVE FC/FCOE CONFIG ( - {fcEntity_api: read_data(dir, "fcEntity_POS.json"), - fabricNode_api: read_data(dir, "fabricNode_POS.json")}, - "6.0(1f)", + {fcEntity_api: read_data(dir, "fcEntity_101_102.json")}, + "6.1(1f)", + read_data(dir, "fabricNode_POS.json"), + script.FAIL_O, + [ + ["topology/pod-1/node-101", "N9K-C93180YC-EX"], + ["topology/pod-1/node-102", "N9K-C93108TC-EX"], + ], + ), + ( + {fcEntity_api: read_data(dir, "fcEntity_101_102.json")}, + "6.0(7e)", + read_data(dir, "fabricNode_POS.json"), + script.FAIL_O, + [ + ["topology/pod-1/node-101", "N9K-C93180YC-EX"], + ["topology/pod-1/node-102", "N9K-C93108TC-EX"], + ], + ), + # FABRIC HAS EX NODES and NONE OF THEM HAVE FC/FCOE CONFIG + ( + {fcEntity_api: []}, + "6.0(7e)", + read_data(dir, "fabricNode_POS.json"), script.PASS, + [], ), - ## FABRIC DOES NOT HAVE EX NODES ( - {fcEntity_api: read_data(dir, "fcEntity_NEG.json"), - fabricNode_api: read_data(dir, "fabricNode_NEG.json")}, - "6.1(1f)", + {fcEntity_api: read_data(dir, "fcEntity_104.json")}, + "6.0(7e)", + read_data(dir, "fabricNode_POS.json"), + script.PASS, + [], + ), + # FABRIC DOES NOT HAVE EX NODES + ( + {fcEntity_api: []}, + "6.0(7e)", + read_data(dir, "fabricNode_NEG.json"), script.PASS, + [], ), ( - {fcEntity_api: read_data(dir, "fcEntity_NEG.json"), - fabricNode_api: read_data(dir, "fabricNode_NEG.json")}, + {fcEntity_api: read_data(dir, "fcEntity_101_102_103.json")}, "6.0(7e)", + read_data(dir, "fabricNode_NEG.json"), script.PASS, + [], ), ( - {fcEntity_api: read_data(dir, "fcEntity_POS.json"), - fabricNode_api: read_data(dir, "fabricNode_NEG.json")}, + {fcEntity_api: read_data(dir, "fcEntity_104.json")}, "6.0(7e)", + read_data(dir, "fabricNode_NEG.json"), script.PASS, + [], ), ], ) -def test_logic(run_check, mock_icurl, tversion, expected_result): - result = run_check(tversion=script.AciVersion(tversion)) +def test_logic(run_check, mock_icurl, tversion, fabric_nodes, expected_result, expected_data): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, + fabric_nodes=fabric_nodes, + ) assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/gen1_switch_compatibility_check/fabricNode_no_gen1.json b/tests/checks/gen1_switch_compatibility_check/fabricNode_no_gen1.json new file mode 100644 index 00000000..d266e8af --- /dev/null +++ b/tests/checks/gen1_switch_compatibility_check/fabricNode_no_gen1.json @@ -0,0 +1,30 @@ +[ + { + "fabricNode": { + "attributes": { + "adSt": "on", + "dn": "topology/pod-2/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C93180YC-FX", + "name": "RL201", + "nodeType": "remote-leaf-wan", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "dn": "topology/pod-2/node-202", + "fabricSt": "active", + "id": "202", + "model": "N9K-C93180YC-FX", + "name": "RL202", + "nodeType": "remote-leaf-wan", + "role": "leaf" + } + } + } +] diff --git a/tests/checks/gen1_switch_compatibility_check/fabricNode_with_gen1.json b/tests/checks/gen1_switch_compatibility_check/fabricNode_with_gen1.json new file mode 100644 index 00000000..e01273b6 --- /dev/null +++ b/tests/checks/gen1_switch_compatibility_check/fabricNode_with_gen1.json @@ -0,0 +1,44 @@ +[ + { + "fabricNode": { + "attributes": { + "adSt": "on", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C9372TX-E", + "name": "Leaf-101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C9372TX-E", + "name": "Leaf-102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "dn": "topology/pod-1/node-1001", + "fabricSt": "active", + "id": "1001", + "model": "N9K-C9332PQ", + "name": "Spine-1001", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] diff --git a/tests/checks/gen1_switch_compatibility_check/test_gen1_switch_compatibility_check.py b/tests/checks/gen1_switch_compatibility_check/test_gen1_switch_compatibility_check.py new file mode 100644 index 00000000..4f076c56 --- /dev/null +++ b/tests/checks/gen1_switch_compatibility_check/test_gen1_switch_compatibility_check.py @@ -0,0 +1,42 @@ +import os +import pytest +import logging +import importlib +from helpers.utils import read_data + +log = logging.getLogger(__name__) +dir = os.path.dirname(os.path.abspath(__file__)) + +script = importlib.import_module("aci-preupgrade-validation-script") +AciVersion = script.AciVersion + +test_function = "gen1_switch_compatibility_check" + + +@pytest.mark.parametrize( + "tversion, fabric_nodes, expected_result, expected_data", + [ + # FAIL - gen1 HW does not support t_ver + ( + "5.2(3b)", + read_data(dir, "fabricNode_with_gen1.json"), + script.FAIL_UF, + [ + ["5.2(3b)", "101", "N9K-C9372TX-E", "Not supported on 5.x+"], + ["5.2(3b)", "102", "N9K-C9372TX-E", "Not supported on 5.x+"], + ["5.2(3b)", "1001", "N9K-C9332PQ", "Not supported on 5.x+"], + ], + ), + # PASS - gen1 HW supports t_ver + ("4.2(7r)", read_data(dir, "fabricNode_with_gen1.json"), script.PASS, []), + # PASS - no gen1 hw found + ("5.2(3b)", read_data(dir, "fabricNode_no_gen1.json"), script.PASS, []), + ], +) +def test_logic(run_check, tversion, fabric_nodes, expected_result, expected_data): + result = run_check( + tversion=AciVersion(tversion) if tversion else None, + fabric_nodes=fabric_nodes, + ) + assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/mini_aci_6_0_2_check/fabricNode_all_phys_apic.json b/tests/checks/mini_aci_6_0_2_check/fabricNode_all_phys_apic.json new file mode 100644 index 00000000..f276f099 --- /dev/null +++ b/tests/checks/mini_aci_6_0_2_check/fabricNode_all_phys_apic.json @@ -0,0 +1,92 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] diff --git a/tests/checks/mini_aci_6_0_2_check/fabricNode_mini_aci.json b/tests/checks/mini_aci_6_0_2_check/fabricNode_mini_aci.json new file mode 100644 index 00000000..de57905a --- /dev/null +++ b/tests/checks/mini_aci_6_0_2_check/fabricNode_mini_aci.json @@ -0,0 +1,92 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "virtual", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "virtual", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] diff --git a/tests/checks/mini_aci_6_0_2_check/test_mini_aci_6_0_2_check.py b/tests/checks/mini_aci_6_0_2_check/test_mini_aci_6_0_2_check.py index a9514461..fe5bb275 100644 --- a/tests/checks/mini_aci_6_0_2_check/test_mini_aci_6_0_2_check.py +++ b/tests/checks/mini_aci_6_0_2_check/test_mini_aci_6_0_2_check.py @@ -11,60 +11,65 @@ test_function = "mini_aci_6_0_2_check" -# icurl queries -topSystems = 'topSystem.json?query-target-filter=wcard(topSystem.role,"controller")' - @pytest.mark.parametrize( - "icurl_outputs, cversion, tversion, expected_result", + "cversion, tversion, fabric_nodes, expected_result, expected_data", [ + # tversion missing + ( + "5.2(3a)", + None, + read_data(dir, "fabricNode_mini_aci.json"), + script.MANUAL, + [], + ), + # Version Not Affected (not crossing 6.0.2) ( - {topSystems: read_data(dir, "topSystem_controller_neg.json")}, "3.2(1a)", "5.2(6a)", - script.PASS, + read_data(dir, "fabricNode_mini_aci.json"), + script.NA, + [], ), + # Version Not Affected (not crossing 6.0.2) ( - {topSystems: read_data(dir, "topSystem_controller_neg.json")}, "6.0(2e)", "6.0(5d)", - script.PASS, + read_data(dir, "fabricNode_mini_aci.json"), + script.NA, + [], ), + # Version Affected, Not mini ACI ( - {topSystems: read_data(dir, "topSystem_controller_neg.json")}, "5.2(3a)", "6.0(3d)", + read_data(dir, "fabricNode_all_phys_apic.json"), script.PASS, + [], ), + # Version Affected, mini ACI ( - {topSystems: read_data(dir, "topSystem_controller_pos.json")}, - "4.1(1a)", - "5.2(7f)", - script.PASS, - ), - ( - {topSystems: read_data(dir, "topSystem_controller_pos.json")}, "4.2(2a)", "6.0(2c)", + read_data(dir, "fabricNode_mini_aci.json"), script.FAIL_UF, + [["2", "apic2", "virtual"], ["3", "apic3", "virtual"]], ), + # Version Affected, mini ACI ( - {topSystems: read_data(dir, "topSystem_controller_pos.json")}, "6.0(1a)", "6.0(2c)", + read_data(dir, "fabricNode_mini_aci.json"), script.FAIL_UF, - ), - ( - {topSystems: read_data(dir, "topSystem_controller_pos.json")}, - "6.0(2c)", - "6.0(5c)", - script.PASS, + [["2", "apic2", "virtual"], ["3", "apic3", "virtual"]], ), ], ) -def test_logic(run_check, mock_icurl, cversion, tversion, expected_result): +def test_logic(run_check, cversion, tversion, fabric_nodes, expected_result, expected_data): result = run_check( cversion=script.AciVersion(cversion), tversion=script.AciVersion(tversion) if tversion else None, + fabric_nodes=fabric_nodes, ) assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json index f1bdd0b5..d9747624 100644 --- a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json +++ b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3H.json @@ -2,24 +2,75 @@ { "fabricNode": { "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.113", "dn": "topology/pod-2/node-113", "fabricSt": "active", "id": "113", "model": "N9K-C93108TC-FX3H", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf113" + "name": "leaf113", + "nodeType": "unspecified", + "role": "leaf" } } }, { "fabricNode": { "attributes": { + "address": "10.0.0.114", "dn": "topology/pod-2/node-114", "fabricSt": "active", "id": "114", "model": "N9K-C93108TC-FX3H", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf114" + "name": "leaf114", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" } } } diff --git a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json index 39538a04..e31c4096 100644 --- a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json +++ b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P.json @@ -2,24 +2,75 @@ { "fabricNode": { "attributes": { - "dn": "topology/pod-1/node-102", + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", "fabricSt": "active", "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.113", + "dn": "topology/pod-2/node-113", + "fabricSt": "active", + "id": "113", "model": "N9K-C93108TC-FX3P", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf101" + "name": "leaf113", + "nodeType": "unspecified", + "role": "leaf" } } }, { "fabricNode": { "attributes": { - "dn": "topology/pod-1/node-102", + "address": "10.0.0.114", + "dn": "topology/pod-2/node-114", "fabricSt": "active", - "id": "102", + "id": "114", "model": "N9K-C93108TC-FX3P", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf102" + "name": "leaf114", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" } } } diff --git a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json index b9d7780e..05a5ffa0 100644 --- a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json +++ b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_FX3P3H.json @@ -2,48 +2,75 @@ { "fabricNode": { "attributes": { - "dn": "topology/pod-1/node-102", - "fabricSt": "active", - "id": "101", - "model": "N9K-C93108TC-FX3P", + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf101" + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" } } }, { "fabricNode": { "attributes": { - "dn": "topology/pod-1/node-102", + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", "fabricSt": "active", - "id": "102", - "model": "N9K-C93108TC-FX3P", + "id": "101", + "model": "N9K-C93180YC-FX", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf102" + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" } } }, { "fabricNode": { "attributes": { + "address": "10.0.0.113", "dn": "topology/pod-2/node-113", "fabricSt": "active", "id": "113", - "model": "N9K-C93108TC-FX3H", + "model": "N9K-C93108TC-FX3P", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf113" + "name": "leaf113", + "nodeType": "unspecified", + "role": "leaf" } } }, { "fabricNode": { "attributes": { + "address": "10.0.0.114", "dn": "topology/pod-2/node-114", "fabricSt": "active", "id": "114", "model": "N9K-C93108TC-FX3H", "monPolDn": "uni/fabric/monfab-default", - "name": "leaf114" + "name": "leaf114", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" } } } diff --git a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_no_FX3P3H.json b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_no_FX3P3H.json new file mode 100644 index 00000000..2d427721 --- /dev/null +++ b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/fabricNode_no_FX3P3H.json @@ -0,0 +1,47 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-1/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine201", + "nodeType": "unspecified", + "role": "spine" + } + } + } +] diff --git a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py index 65b6dd41..e23529c9 100644 --- a/tests/checks/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py +++ b/tests/checks/n9k_c93108tc_fx3p_interface_down_check/test_n9k_c93108tc_fx3p_interface_down_check.py @@ -11,39 +11,83 @@ test_function = "n9k_c93108tc_fx3p_interface_down_check" -# icurl queries -api = 'fabricNode.json?query-target-filter=or(eq(fabricNode.model,"N9K-C93108TC-FX3P"),eq(fabricNode.model,"N9K-C93108TC-FX3H"))' - @pytest.mark.parametrize( - "icurl_outputs, tversion, expected_result", + "tversion, fabric_nodes, expected_result, expected_data", [ # Version not supplied - ({api: []}, None, script.MANUAL), + (None, read_data(dir, "fabricNode_FX3P3H.json"), script.MANUAL, []), # Version not affected - ({api: read_data(dir, "fabricNode_FX3P3H.json")}, "5.2(8h)", script.PASS), - ({api: read_data(dir, "fabricNode_FX3P3H.json")}, "5.3(2b)", script.PASS), - ({api: read_data(dir, "fabricNode_FX3P3H.json")}, "6.0(4c)", script.PASS), + ("5.2(8h)", read_data(dir, "fabricNode_FX3P3H.json"), script.PASS, []), + ("5.3(2b)", read_data(dir, "fabricNode_FX3P3H.json"), script.PASS, []), + ("6.0(4c)", read_data(dir, "fabricNode_FX3P3H.json"), script.PASS, []), # Affected version, no FX3P or FX3H - ({api: []}, "5.2(8g)", script.PASS), - ({api: []}, "5.3(1d)", script.PASS), - ({api: []}, "6.0(2h)", script.PASS), + ("5.2(8g)", read_data(dir, "fabricNode_no_FX3P3H.json"), script.PASS, []), + ("5.3(1d)", read_data(dir, "fabricNode_no_FX3P3H.json"), script.PASS, []), + ("6.0(2h)", read_data(dir, "fabricNode_no_FX3P3H.json"), script.PASS, []), # Affected version, FX3P - ({api: read_data(dir, "fabricNode_FX3P.json")}, "5.2(8g)", script.FAIL_O), - ({api: read_data(dir, "fabricNode_FX3P.json")}, "5.3(1d)", script.FAIL_O), - ({api: read_data(dir, "fabricNode_FX3P.json")}, "6.0(2h)", script.FAIL_O), + ( + "5.2(8g)", + read_data(dir, "fabricNode_FX3P.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3P"], ["114", "leaf114", "N9K-C93108TC-FX3P"]], + ), + ( + "5.3(1d)", + read_data(dir, "fabricNode_FX3P.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3P"], ["114", "leaf114", "N9K-C93108TC-FX3P"]], + ), + ( + "6.0(2h)", + read_data(dir, "fabricNode_FX3P.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3P"], ["114", "leaf114", "N9K-C93108TC-FX3P"]], + ), # Affected version, FX3H - ({api: read_data(dir, "fabricNode_FX3H.json")}, "5.2(8g)", script.FAIL_O), - ({api: read_data(dir, "fabricNode_FX3H.json")}, "5.3(1d)", script.FAIL_O), - ({api: read_data(dir, "fabricNode_FX3H.json")}, "6.0(2h)", script.FAIL_O), + ( + "5.2(8g)", + read_data(dir, "fabricNode_FX3H.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3H"], ["114", "leaf114", "N9K-C93108TC-FX3H"]], + ), + ( + "5.3(1d)", + read_data(dir, "fabricNode_FX3H.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3H"], ["114", "leaf114", "N9K-C93108TC-FX3H"]], + ), + ( + "6.0(2h)", + read_data(dir, "fabricNode_FX3H.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3H"], ["114", "leaf114", "N9K-C93108TC-FX3H"]], + ), # Affected version, FX3P and FX3H - ({api: read_data(dir, "fabricNode_FX3P3H.json")}, "5.2(8g)", script.FAIL_O), - ({api: read_data(dir, "fabricNode_FX3P3H.json")}, "5.3(1d)", script.FAIL_O), - ({api: read_data(dir, "fabricNode_FX3P3H.json")}, "6.0(2h)", script.FAIL_O), + ( + "5.2(8g)", + read_data(dir, "fabricNode_FX3P3H.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3P"], ["114", "leaf114", "N9K-C93108TC-FX3H"]], + ), + ( + "5.3(1d)", + read_data(dir, "fabricNode_FX3P3H.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3P"], ["114", "leaf114", "N9K-C93108TC-FX3H"]], + ), + ( + "6.0(2h)", + read_data(dir, "fabricNode_FX3P3H.json"), + script.FAIL_O, + [["113", "leaf113", "N9K-C93108TC-FX3P"], ["114", "leaf114", "N9K-C93108TC-FX3H"]], + ), ], ) -def test_logic(run_check, mock_icurl, tversion, expected_result): +def test_logic(run_check, tversion, fabric_nodes, expected_result, expected_data): result = run_check( tversion=script.AciVersion(tversion) if tversion else None, + fabric_nodes=fabric_nodes, ) assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/ntp_status_check/NEG_datetimeClkPol.json b/tests/checks/ntp_status_check/NEG_datetimeClkPol.json new file mode 100644 index 00000000..437efa37 --- /dev/null +++ b/tests/checks/ntp_status_check/NEG_datetimeClkPol.json @@ -0,0 +1,46 @@ +{ + "imdata": [ + { + "datetimeClkPol": { + "attributes": { + "StratumValue": "8", + "adminSt": "enabled", + "authSt": "disabled", + "childAction": "", + "clock": "2022-12-07T18:22:33.715-05:00", + "clockRaw": "13206872637755334329", + "descr": "", + "dn": "topology/pod-1/node-201/sys/time", + "flags": "synced", + "lcOwn": "local", + "leap": "0", + "masterMode": "disabled", + "modTs": "2022-01-02T22:36:04.148-05:00", + "monPolDn": "uni/fabric/monfab-Spine-Mon", + "name": "default", + "nameAlias": "", + "ntpdCfgFailedBmp": "", + "ntpdCfgFailedTs": "00:00:00:00.000", + "ntpdCfgState": "0", + "ownerKey": "", + "ownerTag": "", + "peer": "258740908", + "polDn": "uni/fabric/time-default", + "poll": "6", + "precision": "-20", + "refId": "172.18.108.15", + "refName": "172.18.108.15", + "refTime": "2022-12-07T18:21:19.436-05:00", + "refTimeRaw": "8059221958312304239", + "rootDelay": "49", + "rootDispersion": "2315255808", + "serverState": "disabled", + "srvStatus": "synced_remote_server", + "status": "", + "stratum": "2" + } + } + } + ], + "totalCount": "1" +} diff --git a/tests/checks/ntp_status_check/NEG_datetimeNtpq.json b/tests/checks/ntp_status_check/NEG_datetimeNtpq.json new file mode 100644 index 00000000..0b676fe9 --- /dev/null +++ b/tests/checks/ntp_status_check/NEG_datetimeNtpq.json @@ -0,0 +1,29 @@ +{ + "imdata": [ + { + "datetimeNtpq": { + "attributes": { + "auth": "none", + "childAction": "", + "delay": "0.875", + "dn": "topology/pod-1/node-1/sys/ntpq-calo-timeserver-1.cisco.com", + "jitter": "0.027", + "lcOwn": "local", + "modTs": "2022-12-07T18:20:27.571-05:00", + "monPolDn": "uni/fabric/monfab-default", + "offset": "0.004", + "poll": "64", + "reach": "377", + "refid": ".GPS.", + "remote": "calo-timeserver-1.cisco.com", + "status": "", + "stratum": "1", + "t": "u", + "tally": "*", + "when": "16" + } + } + } + ], + "totalCount": "1" +} diff --git a/tests/checks/ntp_status_check/POS_datetimeClkPol.json b/tests/checks/ntp_status_check/POS_datetimeClkPol.json new file mode 100644 index 00000000..5c83dbbb --- /dev/null +++ b/tests/checks/ntp_status_check/POS_datetimeClkPol.json @@ -0,0 +1,46 @@ +{ + "imdata": [ + { + "datetimeClkPol": { + "attributes": { + "StratumValue": "8", + "adminSt": "enabled", + "authSt": "disabled", + "childAction": "", + "clock": "2022-12-07T18:22:33.715-05:00", + "clockRaw": "13206872637755334329", + "descr": "", + "dn": "topology/pod-1/node-201/sys/time", + "flags": "synced", + "lcOwn": "local", + "leap": "0", + "masterMode": "disabled", + "modTs": "2022-01-02T22:36:04.148-05:00", + "monPolDn": "uni/fabric/monfab-Spine-Mon", + "name": "default", + "nameAlias": "", + "ntpdCfgFailedBmp": "", + "ntpdCfgFailedTs": "00:00:00:00.000", + "ntpdCfgState": "0", + "ownerKey": "", + "ownerTag": "", + "peer": "258740908", + "polDn": "uni/fabric/time-default", + "poll": "6", + "precision": "-20", + "refId": "172.18.108.15", + "refName": "172.18.108.15", + "refTime": "2022-12-07T18:21:19.436-05:00", + "refTimeRaw": "8059221958312304239", + "rootDelay": "49", + "rootDispersion": "2315255808", + "serverState": "disabled", + "srvStatus": "", + "status": "", + "stratum": "2" + } + } + } + ], + "totalCount": "1" +} diff --git a/tests/checks/ntp_status_check/POS_datetimeNtpq.json b/tests/checks/ntp_status_check/POS_datetimeNtpq.json new file mode 100644 index 00000000..698e516a --- /dev/null +++ b/tests/checks/ntp_status_check/POS_datetimeNtpq.json @@ -0,0 +1,29 @@ +{ + "imdata": [ + { + "datetimeNtpq": { + "attributes": { + "auth": "none", + "childAction": "", + "delay": "0.875", + "dn": "topology/pod-1/node-1/sys/ntpq-calo-timeserver-1.cisco.com", + "jitter": "0.027", + "lcOwn": "local", + "modTs": "2022-12-07T18:20:27.571-05:00", + "monPolDn": "uni/fabric/monfab-default", + "offset": "0.004", + "poll": "64", + "reach": "377", + "refid": ".GPS.", + "remote": "calo-timeserver-1.cisco.com", + "status": "", + "stratum": "1", + "t": "u", + "tally": "", + "when": "16" + } + } + } + ], + "totalCount": "1" +} diff --git a/tests/checks/ntp_status_check/fabricNode.json b/tests/checks/ntp_status_check/fabricNode.json new file mode 100644 index 00000000..ce488e69 --- /dev/null +++ b/tests/checks/ntp_status_check/fabricNode.json @@ -0,0 +1,64 @@ +[ + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.0.1", + "annotation": "", + "apicType": "apic", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-1/node-1", + "extMngdBy": "", + "fabricSt": "unknown", + "id": "1", + "lastStateModTs": "2022-11-14T13:36:15.401-05:00", + "lcOwn": "local", + "modTs": "2022-11-14T13:36:16.119-05:00", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "fab3-apic1", + "nameAlias": "", + "nodeType": "unspecified", + "role": "controller", + "serial": "FOX1234ABCE", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "5.2(7f)" + } + } + }, + { + "fabricNode": { + "attributes": { + "adSt": "on", + "address": "10.0.128.65", + "annotation": "", + "apicType": "apic", + "childAction": "", + "delayedHeartbeat": "no", + "dn": "topology/pod-1/node-201", + "extMngdBy": "", + "fabricSt": "active", + "id": "201", + "lastStateModTs": "2022-03-30T11:34:22.529-05:00", + "lcOwn": "local", + "modTs": "2022-03-30T11:34:34.616-05:00", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "fab3-spine201", + "nameAlias": "", + "nodeType": "unspecified", + "role": "spine", + "serial": "FOX1234ABCD", + "status": "", + "uid": "0", + "userdom": "all", + "vendor": "Cisco Systems, Inc", + "version": "n9000-14.2(6d)" + } + } + } +] diff --git a/tests/checks/ntp_status_check/test_ntp_status_check.py b/tests/checks/ntp_status_check/test_ntp_status_check.py new file mode 100644 index 00000000..3fff822b --- /dev/null +++ b/tests/checks/ntp_status_check/test_ntp_status_check.py @@ -0,0 +1,65 @@ +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__)) + +test_function = "ntp_status_check" + +# icurl queries +apic_ntp = "datetimeNtpq.json" +switch_ntp = "datetimeClkPol.json" + + +@pytest.mark.parametrize( + "icurl_outputs, expected_result, expected_data", + [ + # FAIL - APIC is not Synced + ( + { + apic_ntp: read_data(dir, "POS_datetimeNtpq.json"), + switch_ntp: read_data(dir, "NEG_datetimeClkPol.json"), + }, + script.FAIL_UF, + [["1", "1"]], + ), + # FAIL - Switch is not Synced + ( + { + apic_ntp: read_data(dir, "NEG_datetimeNtpq.json"), + switch_ntp: read_data(dir, "POS_datetimeClkPol.json"), + }, + script.FAIL_UF, + [["1", "201"]], + ), + # FAIL - APIC and Switch are not Synced + ( + { + apic_ntp: read_data(dir, "POS_datetimeNtpq.json"), + switch_ntp: read_data(dir, "POS_datetimeClkPol.json"), + }, + script.FAIL_UF, + [["1", "1"], ["1", "201"]], + ), + # PASS - Both are synced + ( + { + apic_ntp: read_data(dir, "NEG_datetimeNtpq.json"), + switch_ntp: read_data(dir, "NEG_datetimeClkPol.json"), + }, + script.PASS, + [], + ), + ], +) +def test_logic(run_check, mock_icurl, expected_result, expected_data): + result = run_check( + fabric_nodes=read_data(dir, "fabricNode.json"), + ) + assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/observer_db_size_check/fabricNode.json b/tests/checks/observer_db_size_check/fabricNode.json new file mode 100644 index 00000000..21eed6a3 --- /dev/null +++ b/tests/checks/observer_db_size_check/fabricNode.json @@ -0,0 +1,46 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "id": "1", + "name": "apic1", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "id": "2", + "name": "apic2", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-1/node-3", + "id": "3", + "name": "apic3", + "role": "controller" + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "id": "101", + "name": "leaf1", + "role": "leaf" + } + } + } +] diff --git a/tests/checks/observer_db_size_check/fabricNode_no_apic.json b/tests/checks/observer_db_size_check/fabricNode_no_apic.json new file mode 100644 index 00000000..b82c912f --- /dev/null +++ b/tests/checks/observer_db_size_check/fabricNode_no_apic.json @@ -0,0 +1,13 @@ +[ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "id": "101", + "name": "fab5-leaf1", + "role": "leaf" + } + } + } +] diff --git a/tests/checks/observer_db_size_check/test_observer_db_size_check.py b/tests/checks/observer_db_size_check/test_observer_db_size_check.py index 21bfa916..022dba84 100644 --- a/tests/checks/observer_db_size_check/test_observer_db_size_check.py +++ b/tests/checks/observer_db_size_check/test_observer_db_size_check.py @@ -11,13 +11,11 @@ test_function = "observer_db_size_check" -topSystem_api = 'topSystem.json' -topSystem_api += '?query-target-filter=eq(topSystem.role,"controller")' - -topSystem = read_data(dir, "topSystem.json") +fabricNodes = read_data(dir, "fabricNode.json") apic_ips = [ - mo["topSystem"]["attributes"]["address"] - for mo in topSystem["imdata"] + mo["fabricNode"]["attributes"]["address"] + for mo in fabricNodes + if mo["fabricNode"]["attributes"]["role"] == "controller" ] ls_cmd = "ls -lh /data2/dbstats | awk '{print $5, $9}'" @@ -44,18 +42,23 @@ @pytest.mark.parametrize( - "icurl_outputs, conn_failure, conn_cmds, expected_result", + "fabric_nodes, conn_failure, conn_cmds, expected_result, expected_data", [ # Connection failure ( - {topSystem_api: read_data(dir, "topSystem.json")}, + fabricNodes, True, [], script.ERROR, + [ + ["1", "apic1", "Simulated exception at connect()"], + ["2", "apic2", "Simulated exception at connect()"], + ["3", "apic3", "Simulated exception at connect()"], + ], ), # Simulatated exception at `ls` command ( - {topSystem_api: read_data(dir, "topSystem.json")}, + fabricNodes, False, { apic_ip: [ @@ -68,10 +71,15 @@ for apic_ip in apic_ips }, script.ERROR, + [ + ["1", "apic1", "Simulated exception at `ls` command"], + ["2", "apic2", "Simulated exception at `ls` command"], + ["3", "apic3", "Simulated exception at `ls` command"], + ], ), # dbstats dir not found/not accessible ( - {topSystem_api: read_data(dir, "topSystem.json")}, + fabricNodes, False, { apic_ip: [ @@ -84,10 +92,15 @@ for apic_ip in apic_ips }, script.ERROR, + [ + ["1", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], + ["2", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], + ["3", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], + ], ), # dbstats dir found, all DBs under 1G ( - {topSystem_api: read_data(dir, "topSystem.json")}, + fabricNodes, False, { apic_ip: [ @@ -100,10 +113,11 @@ for apic_ip in apic_ips }, script.PASS, + [], ), # dbstats dir found, found DBs over 1G ( - {topSystem_api: read_data(dir, "topSystem.json")}, + fabricNodes, False, { apic_ip: [ @@ -116,16 +130,30 @@ for apic_ip in apic_ips }, script.FAIL_UF, + [ + ["1", "/data2/dbstats/observer_8.db", "1.0G"], + ["1", "/data2/dbstats/observer_9.db", "12G"], + ["2", "/data2/dbstats/observer_8.db", "1.0G"], + ["2", "/data2/dbstats/observer_9.db", "12G"], + ["3", "/data2/dbstats/observer_8.db", "1.0G"], + ["3", "/data2/dbstats/observer_9.db", "12G"], + ], ), - # ERROR, topsystem failure + # ERROR, fabricNode failure ( - {topSystem_api: read_data(dir, "topSystem_empty.json")}, + read_data(dir, "fabricNode_no_apic.json"), False, [], script.ERROR, + [], ), ], ) -def test_logic(run_check, mock_icurl, mock_conn, expected_result): - result = run_check(username="fake_username", password="fake_password") +def test_logic(run_check, fabric_nodes, mock_conn, expected_result, expected_data): + result = run_check( + username="fake_username", + password="fake_password", + fabric_nodes=fabric_nodes, + ) assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/observer_db_size_check/topSystem.json b/tests/checks/observer_db_size_check/topSystem.json deleted file mode 100644 index 5b265e92..00000000 --- a/tests/checks/observer_db_size_check/topSystem.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "totalCount": "3", - "imdata": [ - { - "topSystem": { - "attributes": { - "address": "10.0.0.1", - "dn": "topology/pod-1/node-1/sys", - "id": "1", - "name": "fab5-apic1" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.0.0.2", - "dn": "topology/pod-1/node-2/sys", - "id": "2", - "name": "fab5-apic2" - } - } - }, - { - "topSystem": { - "attributes": { - "address": "10.0.0.3", - "dn": "topology/pod-1/node-3/sys", - "id": "3", - "name": "fab5-apic3" - } - } - } - ] -} \ No newline at end of file diff --git a/tests/checks/observer_db_size_check/topSystem_empty.json b/tests/checks/observer_db_size_check/topSystem_empty.json deleted file mode 100644 index 0637a088..00000000 --- a/tests/checks/observer_db_size_check/topSystem_empty.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/tests/checks/r_leaf_compatibility_check/fabricNode_no_RL.json b/tests/checks/r_leaf_compatibility_check/fabricNode_no_RL.json new file mode 100644 index 00000000..025af757 --- /dev/null +++ b/tests/checks/r_leaf_compatibility_check/fabricNode_no_RL.json @@ -0,0 +1,123 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "id": "1", + "name": "apic1", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-2", + "id": "2", + "name": "apic2", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-3", + "id": "3", + "name": "apic3", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "id": "101", + "name": "LF101", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-102", + "id": "102", + "name": "LF102", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-103", + "id": "103", + "name": "LF103", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-104", + "id": "104", + "name": "LF104", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-111", + "id": "111", + "name": "T2_LF111", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-112", + "id": "112", + "name": "T2_LF112", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1001", + "id": "1001", + "name": "SP1001", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1002", + "id": "1002", + "name": "SP1002", + "role": "spine", + "nodeType": "unspecified" + } + } + } +] diff --git a/tests/checks/r_leaf_compatibility_check/fabricNode_with_RL.json b/tests/checks/r_leaf_compatibility_check/fabricNode_with_RL.json new file mode 100644 index 00000000..73e7d5b8 --- /dev/null +++ b/tests/checks/r_leaf_compatibility_check/fabricNode_with_RL.json @@ -0,0 +1,145 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "id": "1", + "name": "apic1", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-2", + "id": "2", + "name": "apic2", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-3", + "id": "3", + "name": "apic3", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "id": "101", + "name": "LF101", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-102", + "id": "102", + "name": "LF102", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-103", + "id": "103", + "name": "LF103", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-104", + "id": "104", + "name": "LF104", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-111", + "id": "111", + "name": "T2_LF111", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-112", + "id": "112", + "name": "T2_LF112", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-121", + "id": "121", + "name": "RL_LF121", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-122", + "id": "122", + "name": "RL_LF122", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1001", + "id": "1001", + "name": "SP1001", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1002", + "id": "1002", + "name": "SP1002", + "role": "spine", + "nodeType": "unspecified" + } + } + } +] diff --git a/tests/checks/r_leaf_compatibility_check/infraSetPol_DTF_disabled.json b/tests/checks/r_leaf_compatibility_check/infraSetPol_DTF_disabled.json new file mode 100644 index 00000000..fd88e044 --- /dev/null +++ b/tests/checks/r_leaf_compatibility_check/infraSetPol_DTF_disabled.json @@ -0,0 +1,13 @@ +{ + "totalCount": "1", + "imdata": [ + { + "infraSetPol": { + "attributes": { + "dn": "uni/infra/settings", + "enableRemoteLeafDirect": "no" + } + } + } + ] +} diff --git a/tests/checks/r_leaf_compatibility_check/infraSetPol_DTF_enabled.json b/tests/checks/r_leaf_compatibility_check/infraSetPol_DTF_enabled.json new file mode 100644 index 00000000..52e0a3e5 --- /dev/null +++ b/tests/checks/r_leaf_compatibility_check/infraSetPol_DTF_enabled.json @@ -0,0 +1,13 @@ +{ + "totalCount": "1", + "imdata": [ + { + "infraSetPol": { + "attributes": { + "dn": "uni/infra/settings", + "enableRemoteLeafDirect": "yes" + } + } + } + ] +} diff --git a/tests/checks/r_leaf_compatibility_check/infraSetPol_no_DTF.json b/tests/checks/r_leaf_compatibility_check/infraSetPol_no_DTF.json new file mode 100644 index 00000000..0b6f56b9 --- /dev/null +++ b/tests/checks/r_leaf_compatibility_check/infraSetPol_no_DTF.json @@ -0,0 +1,12 @@ +{ + "totalCount": "1", + "imdata": [ + { + "infraSetPol": { + "attributes": { + "dn": "uni/infra/settings" + } + } + } + ] +} diff --git a/tests/checks/r_leaf_compatibility_check/test_r_leaf_compatibility_check.py b/tests/checks/r_leaf_compatibility_check/test_r_leaf_compatibility_check.py new file mode 100644 index 00000000..b5d53ab3 --- /dev/null +++ b/tests/checks/r_leaf_compatibility_check/test_r_leaf_compatibility_check.py @@ -0,0 +1,103 @@ +import os +import pytest +import logging +import importlib +from helpers.utils import read_data + +log = logging.getLogger(__name__) +dir = os.path.dirname(os.path.abspath(__file__)) + +script = importlib.import_module("aci-preupgrade-validation-script") +AciVersion = script.AciVersion + +test_function = "r_leaf_compatibility_check" + +# icurl queries +infraSetPol = "uni/infra/settings.json" + + +@pytest.mark.parametrize( + "icurl_outputs, fabric_nodes, cversion, tversion, expected_result, expected_data", + [ + # MANUAL - No TVER + ( + {infraSetPol: read_data(dir, "infraSetPol_no_DTF.json")}, + read_data(dir, "fabricNode_with_RL.json"), + "4.1(1a)", + None, + script.MANUAL, + [], + ), + # FAIL - pre DTF version, yes RL + ( + {infraSetPol: read_data(dir, "infraSetPol_no_DTF.json")}, + read_data(dir, "fabricNode_with_RL.json"), + "4.1(1a)", + "5.2(1a)", + script.FAIL_O, + [["5.2(1a)", "Present", "Not Supported"]], + ), + # PASS - pre DTF version, no RL + ( + {infraSetPol: read_data(dir, "infraSetPol_no_DTF.json")}, + read_data(dir, "fabricNode_no_RL.json"), + "4.1(1a)", + "5.2(1a)", + script.NA, + [], + ), + # FAIL - bug version upgrade, yes RL + ( + {infraSetPol: read_data(dir, "infraSetPol_DTF_enabled.json")}, + read_data(dir, "fabricNode_with_RL.json"), + "4.1(2a)", + "4.2(2a)", + script.FAIL_O, + [["4.2(2a)", "Present", True]], + ), + # PASS - bug version upgrade, no RL + ( + {infraSetPol: read_data(dir, "infraSetPol_DTF_enabled.json")}, + read_data(dir, "fabricNode_no_RL.json"), + "4.1(2a)", + "4.2(2a)", + script.NA, + [], + ), + # PASS - Fix ver to 5.x, yes RL, DTF enabled + ( + {infraSetPol: read_data(dir, "infraSetPol_DTF_enabled.json")}, + read_data(dir, "fabricNode_with_RL.json"), + "4.2(3a)", + "5.2(3a)", + script.PASS, + [], + ), + # FAIL - Fix ver to 5.x, yes RL, DTF disabled + ( + {infraSetPol: read_data(dir, "infraSetPol_DTF_disabled.json")}, + read_data(dir, "fabricNode_with_RL.json"), + "4.2(3a)", + "5.2(3a)", + script.FAIL_O, + [["5.2(3a)", "Present", False]], + ), + # PASS - Fix ver to 5.x, no RL + ( + {infraSetPol: read_data(dir, "infraSetPol_DTF_disabled.json")}, + read_data(dir, "fabricNode_no_RL.json"), + "4.2(3a)", + "5.2(3a)", + script.NA, + [], + ), + ], +) +def test_logic(run_check, mock_icurl, fabric_nodes, cversion, tversion, expected_result, expected_data): + result = run_check( + cversion=AciVersion(cversion), + tversion=AciVersion(tversion) if tversion else None, + fabric_nodes=fabric_nodes, + ) + assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/stale_decomissioned_spine_check/fabricNode.json b/tests/checks/stale_decomissioned_spine_check/fabricNode.json new file mode 100644 index 00000000..7a79d17c --- /dev/null +++ b/tests/checks/stale_decomissioned_spine_check/fabricNode.json @@ -0,0 +1,46 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "id": "101", + "fabricSt": "active", + "role": "leaf", + "name": "leaf1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-201", + "id": "201", + "fabricSt": "active", + "role": "spine", + "name": "spine1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-106", + "id": "106", + "fabricSt": "active", + "role": "spine", + "name": "spine2" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-107", + "id": "107", + "fabricSt": "disabled", + "role": "spine", + "name": "spine3" + } + } + } +] diff --git a/tests/checks/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json b/tests/checks/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json deleted file mode 100644 index 0637a088..00000000 --- a/tests/checks/stale_decomissioned_spine_check/fabricRsDecommissionNode_NEG.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/tests/checks/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py b/tests/checks/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py index fff0653d..7d9c6f98 100644 --- a/tests/checks/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py +++ b/tests/checks/stale_decomissioned_spine_check/test_stale_decomissioned_spine_check.py @@ -12,55 +12,46 @@ test_function = "stale_decomissioned_spine_check" # icurl queries -decomissioned_api = 'fabricRsDecommissionNode.json' - -active_spine_api = 'topSystem.json' -active_spine_api += '?query-target-filter=eq(topSystem.role,"spine")' +decomissioned_api = "fabricRsDecommissionNode.json" @pytest.mark.parametrize( - "icurl_outputs, tversion, expected_result", + "icurl_outputs, tversion, expected_result, expected_data", [ # TVERSION not supplied ( - { - active_spine_api: read_data(dir, "topSystem.json"), - decomissioned_api: read_data(dir,"fabricRsDecommissionNode_NEG.json") - }, + {decomissioned_api: read_data(dir, "fabricRsDecommissionNode_POS.json")}, None, script.MANUAL, + [], ), # No decom objects ( - { - active_spine_api: read_data(dir, "topSystem.json"), - decomissioned_api: read_data(dir,"fabricRsDecommissionNode_NEG.json") - }, + {decomissioned_api: []}, "5.2(5e)", script.PASS, + [], ), # Spine has stale decom object, and going to affected version ( - { - active_spine_api: read_data(dir, "topSystem.json"), - decomissioned_api: read_data(dir,"fabricRsDecommissionNode_POS.json") - }, + {decomissioned_api: read_data(dir, "fabricRsDecommissionNode_POS.json")}, "5.2(6a)", script.FAIL_O, + [["106", "spine2", "active"]], ), # Fixed Target Version ( - { - active_spine_api: read_data(dir, "topSystem.json"), - decomissioned_api: read_data(dir,"fabricRsDecommissionNode_POS.json") - }, + {decomissioned_api: read_data(dir, "fabricRsDecommissionNode_POS.json")}, "6.0(4a)", script.PASS, + [], ), ], ) -def test_logic(run_check, mock_icurl, tversion, expected_result): +def test_logic(run_check, mock_icurl, tversion, expected_result, expected_data): result = run_check( tversion=script.AciVersion(tversion) if tversion else None, + fabric_nodes=read_data(dir, "fabricNode.json"), ) assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/stale_decomissioned_spine_check/topSystem.json b/tests/checks/stale_decomissioned_spine_check/topSystem.json deleted file mode 100644 index 38d8d14b..00000000 --- a/tests/checks/stale_decomissioned_spine_check/topSystem.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-201/sys", - "id": "201", - "state": "in-service", - "role": "spine", - "name": "spine1" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-106/sys", - "id": "106", - "state": "in-service", - "role": "spine", - "name": "spine2" - } - } - } - ] \ No newline at end of file diff --git a/tests/checks/switch_group_guideline_check/bgpRRNodePEp_1001_1002_2001_2002.json b/tests/checks/switch_group_guideline_check/bgpRRNodePEp_1001_1002_2001_2002.json new file mode 100644 index 00000000..dfd90b26 --- /dev/null +++ b/tests/checks/switch_group_guideline_check/bgpRRNodePEp_1001_1002_2001_2002.json @@ -0,0 +1,38 @@ +[ + { + "bgpRRNodePEp": { + "attributes": { + "dn": "uni/fabric/bgpInstP-default/rr/node-1001", + "id": "1001", + "podId": "1" + } + } + }, + { + "bgpRRNodePEp": { + "attributes": { + "dn": "uni/fabric/bgpInstP-default/rr/node-1002", + "id": "1002", + "podId": "1" + } + } + }, + { + "bgpRRNodePEp": { + "attributes": { + "dn": "uni/fabric/bgpInstP-default/rr/node-2001", + "id": "2001", + "podId": "2" + } + } + }, + { + "bgpRRNodePEp": { + "attributes": { + "dn": "uni/fabric/bgpInstP-default/rr/node-2002", + "id": "2002", + "podId": "2" + } + } + } +] diff --git a/tests/checks/switch_group_guideline_check/fabricExplicitGEp.json b/tests/checks/switch_group_guideline_check/fabricExplicitGEp.json new file mode 100644 index 00000000..05cb7bc9 --- /dev/null +++ b/tests/checks/switch_group_guideline_check/fabricExplicitGEp.json @@ -0,0 +1,92 @@ +[ + { + "fabricExplicitGEp": { + "attributes": { + "dn": "uni/fabric/protpol/expgep-101-102", + "id": "12", + "name": "101-102", + "virtualIp": "10.0.112.64/32" + }, + "children": [ + { + "fabricNodePEp": { + "attributes": { + "id": "102", + "podId": "1", + "rn": "nodepep-102" + } + } + }, + { + "fabricNodePEp": { + "attributes": { + "id": "101", + "podId": "1", + "rn": "nodepep-101" + } + } + } + ] + } + }, + { + "fabricExplicitGEp": { + "attributes": { + "dn": "uni/fabric/protpol/expgep-111-112", + "id": "112", + "name": "111-112", + "virtualIp": "10.0.188.64/32" + }, + "children": [ + { + "fabricNodePEp": { + "attributes": { + "id": "111", + "podId": "1", + "rn": "nodepep-111" + } + } + }, + { + "fabricNodePEp": { + "attributes": { + "id": "112", + "podId": "1", + "rn": "nodepep-112" + } + } + } + ] + } + }, + { + "fabricExplicitGEp": { + "attributes": { + "dn": "uni/fabric/protpol/expgep-201-202", + "id": "212", + "name": "201-202", + "virtualIp": "10.0.188.64/32" + }, + "children": [ + { + "fabricNodePEp": { + "attributes": { + "id": "201", + "podId": "2", + "rn": "nodepep-201" + } + } + }, + { + "fabricNodePEp": { + "attributes": { + "id": "202", + "podId": "2", + "rn": "nodepep-202" + } + } + } + ] + } + } +] diff --git a/tests/checks/switch_group_guideline_check/fabricNode.json b/tests/checks/switch_group_guideline_check/fabricNode.json new file mode 100644 index 00000000..09e4c84c --- /dev/null +++ b/tests/checks/switch_group_guideline_check/fabricNode.json @@ -0,0 +1,206 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "name": "apic1", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "name": "apic2", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "name": "apic3", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "name": "LF101", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "name": "LF102", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-2/node-201", + "fabricSt": "active", + "id": "201", + "name": "LF201", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-2/node-202", + "fabricSt": "active", + "id": "202", + "name": "LF202", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-111", + "fabricSt": "active", + "id": "111", + "name": "T2_LF111", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-112", + "fabricSt": "active", + "id": "112", + "name": "T2_LF112", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-121", + "fabricSt": "active", + "id": "121", + "name": "RL_LF121", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-122", + "fabricSt": "active", + "id": "122", + "name": "RL_LF122", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1001", + "fabricSt": "active", + "id": "1001", + "name": "SP1001", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1002", + "fabricSt": "active", + "id": "1002", + "name": "SP1002", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1003", + "fabricSt": "active", + "id": "1003", + "name": "SP1003", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1004", + "fabricSt": "active", + "id": "1004", + "name": "SP1004", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-2/node-2001", + "fabricSt": "active", + "id": "2001", + "name": "SP2001", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-2/node-2002", + "fabricSt": "active", + "id": "2002", + "name": "SP2002", + "role": "spine", + "nodeType": "unspecified" + } + } + } +] diff --git a/tests/checks/switch_group_guideline_check/l3extRsNodeL3OutAtt_1001_1002_2001_2002.json b/tests/checks/switch_group_guideline_check/l3extRsNodeL3OutAtt_1001_1002_2001_2002.json new file mode 100644 index 00000000..5ecfc3f7 --- /dev/null +++ b/tests/checks/switch_group_guideline_check/l3extRsNodeL3OutAtt_1001_1002_2001_2002.json @@ -0,0 +1,50 @@ +[ + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_121/rsnodeL3OutAtt-[topology/pod-1/node-121]", + "tDn": "topology/pod-1/node-121" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_122/rsnodeL3OutAtt-[topology/pod-1/node-122]", + "tDn": "topology/pod-1/node-122" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_1001/rsnodeL3OutAtt-[topology/pod-1/node-1001]", + "tDn": "topology/pod-1/node-1001" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_1002/rsnodeL3OutAtt-[topology/pod-1/node-1002]", + "tDn": "topology/pod-1/node-1002" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_2001/rsnodeL3OutAtt-[topology/pod-2/node-2001]", + "tDn": "topology/pod-2/node-2001" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-node-2002-profile/rsnodeL3OutAtt-[topology/pod-2/node-2002]", + "tDn": "topology/pod-2/node-2002" + } + } + } +] diff --git a/tests/checks/switch_group_guideline_check/l3extRsNodeL3OutAtt_1003_1004_2001_2002.json b/tests/checks/switch_group_guideline_check/l3extRsNodeL3OutAtt_1003_1004_2001_2002.json new file mode 100644 index 00000000..f7af8e8f --- /dev/null +++ b/tests/checks/switch_group_guideline_check/l3extRsNodeL3OutAtt_1003_1004_2001_2002.json @@ -0,0 +1,50 @@ +[ + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_121/rsnodeL3OutAtt-[topology/pod-1/node-121]", + "tDn": "topology/pod-1/node-121" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_122/rsnodeL3OutAtt-[topology/pod-1/node-122]", + "tDn": "topology/pod-1/node-122" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_1003/rsnodeL3OutAtt-[topology/pod-1/node-1003]", + "tDn": "topology/pod-1/node-1003" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_1004/rsnodeL3OutAtt-[topology/pod-1/node-1004]", + "tDn": "topology/pod-1/node-1004" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-LNodeP_2001/rsnodeL3OutAtt-[topology/pod-2/node-2001]", + "tDn": "topology/pod-2/node-2001" + } + } + }, + { + "l3extRsNodeL3OutAtt": { + "attributes": { + "dn": "uni/tn-infra/out-multipodL3Out/lnodep-node-2002-profile/rsnodeL3OutAtt-[topology/pod-2/node-2002]", + "tDn": "topology/pod-2/node-2002" + } + } + } +] diff --git a/tests/checks/switch_group_guideline_check/lldpCtrlrAdjEp.json b/tests/checks/switch_group_guideline_check/lldpCtrlrAdjEp.json new file mode 100644 index 00000000..139f5bdd --- /dev/null +++ b/tests/checks/switch_group_guideline_check/lldpCtrlrAdjEp.json @@ -0,0 +1,62 @@ +[ + { + "lldpCtrlrAdjEp": { + "attributes": { + "apicMode": "active", + "dn": "topology/pod-1/node-101/sys/lldp/inst/if-[eth1/1]/ctrlradj", + "id": "1", + "portRole": "active" + } + } + }, + { + "lldpCtrlrAdjEp": { + "attributes": { + "apicMode": "active", + "dn": "topology/pod-1/node-101/sys/lldp/inst/if-[eth1/2]/ctrlradj", + "id": "2", + "portRole": "active" + } + } + }, + { + "lldpCtrlrAdjEp": { + "attributes": { + "apicMode": "active", + "dn": "topology/pod-1/node-102/sys/lldp/inst/if-[eth1/2]/ctrlradj", + "id": "2", + "portRole": "backup" + } + } + }, + { + "lldpCtrlrAdjEp": { + "attributes": { + "apicMode": "active", + "dn": "topology/pod-1/node-102/sys/lldp/inst/if-[eth1/1]/ctrlradj", + "id": "1", + "portRole": "backup" + } + } + }, + { + "lldpCtrlrAdjEp": { + "attributes": { + "apicMode": "active", + "dn": "topology/pod-2/node-201/sys/lldp/inst/if-[eth1/3]/ctrlradj", + "id": "3", + "portRole": "active" + } + } + }, + { + "lldpCtrlrAdjEp": { + "attributes": { + "apicMode": "active", + "dn": "topology/pod-2/node-202/sys/lldp/inst/if-[eth1/3]/ctrlradj", + "id": "3", + "portRole": "active" + } + } + } +] diff --git a/tests/checks/switch_group_guideline_check/maintMaintGrp_ALL.json b/tests/checks/switch_group_guideline_check/maintMaintGrp_ALL.json new file mode 100644 index 00000000..74719f4e --- /dev/null +++ b/tests/checks/switch_group_guideline_check/maintMaintGrp_ALL.json @@ -0,0 +1,94 @@ +[ + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-ALL", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "ALL", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-ALL", + "tnMaintMaintPName": "ALL" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk101-101", "from_": "101", "to_": "101"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk102-102", "from_": "102", "to_": "102"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk111-111", "from_": "111", "to_": "111"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk112-112", "from_": "112", "to_": "112"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk121-121", "from_": "121", "to_": "121"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk122-122", "from_": "122", "to_": "122"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk201-201", "from_": "201", "to_": "201"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk202-202", "from_": "202", "to_": "202"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1001-1001", "from_": "1001", "to_": "1001"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1002-1002", "from_": "1002", "to_": "1002"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1003-1003", "from_": "1003", "to_": "1003"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1004-1004", "from_": "1004", "to_": "1004"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2001-2001", "from_": "2001", "to_": "2001"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2002-2002", "from_": "2002", "to_": "2002"} + } + } + ] + } + } +] diff --git a/tests/checks/switch_group_guideline_check/maintMaintGrp_BAD_GRP1_GRP2.json b/tests/checks/switch_group_guideline_check/maintMaintGrp_BAD_GRP1_GRP2.json new file mode 100644 index 00000000..ed909d9e --- /dev/null +++ b/tests/checks/switch_group_guideline_check/maintMaintGrp_BAD_GRP1_GRP2.json @@ -0,0 +1,116 @@ +[ + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-GRP1", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "GRP1", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-GRP1", + "tnMaintMaintPName": "GRP1" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk101-101", "from_": "101", "to_": "101"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk102-102", "from_": "102", "to_": "102"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk121-121", "from_": "121", "to_": "121"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk122-122", "from_": "122", "to_": "122"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1001-1001", "from_": "1001", "to_": "1001"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1002-1002", "from_": "1002", "to_": "1002"} + } + } + ] + } + }, + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-GRP2", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "GRP2", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-GRP2", + "tnMaintMaintPName": "GRP2" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk111-111", "from_": "111", "to_": "111"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk112-112", "from_": "112", "to_": "112"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk201-201", "from_": "201", "to_": "201"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk202-202", "from_": "202", "to_": "202"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1003-1003", "from_": "1003", "to_": "1003"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1004-1004", "from_": "1004", "to_": "1004"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2001-2001", "from_": "2001", "to_": "2001"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2002-2002", "from_": "2002", "to_": "2002"} + } + } + ] + } + } +] diff --git a/tests/checks/switch_group_guideline_check/maintMaintGrp_BAD_ONLY_POD1_SPINE_RR.json b/tests/checks/switch_group_guideline_check/maintMaintGrp_BAD_ONLY_POD1_SPINE_RR.json new file mode 100644 index 00000000..dace96c7 --- /dev/null +++ b/tests/checks/switch_group_guideline_check/maintMaintGrp_BAD_ONLY_POD1_SPINE_RR.json @@ -0,0 +1,160 @@ +[ + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-SPINE_GRP1", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "SPINE_GRP1", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-SPINE_GRP1", + "tnMaintMaintPName": "SPINE_GRP1" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1001-1001", "from_": "1001", "to_": "1001"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1002-1002", "from_": "1002", "to_": "1002"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1003-1003", "from_": "1003", "to_": "1003"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2001-2001", "from_": "2001", "to_": "2001"} + } + } + ] + } + }, + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-SPINE_GRP2", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "SPINE_GRP2", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-SPINE_GRP2", + "tnMaintMaintPName": "SPINE_GRP2" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1004-1004", "from_": "1004", "to_": "1004"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2002-2002", "from_": "2002", "to_": "2002"} + } + } + ] + } + }, + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-ODD", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "ODD", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-ODD", + "tnMaintMaintPName": "ODD" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk101-101", "from_": "101", "to_": "101"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk111-111", "from_": "111", "to_": "111"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk121-121", "from_": "121", "to_": "121"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk201-201", "from_": "201", "to_": "201"} + } + } + ] + } + }, + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-EVEN", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "EVEN", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-EVEN", + "tnMaintMaintPName": "EVEN" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk102-102", "from_": "102", "to_": "102"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk112-112", "from_": "112", "to_": "112"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk122-122", "from_": "122", "to_": "122"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk202-202", "from_": "202", "to_": "202"} + } + } + ] + } + } +] diff --git a/tests/checks/switch_group_guideline_check/maintMaintGrp_EVEN_ODD.json b/tests/checks/switch_group_guideline_check/maintMaintGrp_EVEN_ODD.json new file mode 100644 index 00000000..678fff38 --- /dev/null +++ b/tests/checks/switch_group_guideline_check/maintMaintGrp_EVEN_ODD.json @@ -0,0 +1,116 @@ +[ + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-ODD", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "ODD", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-ODD", + "tnMaintMaintPName": "ODD" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk101-101", "from_": "101", "to_": "101"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk111-111", "from_": "111", "to_": "111"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk121-121", "from_": "121", "to_": "121"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk201-201", "from_": "201", "to_": "201"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1001-1001", "from_": "1001", "to_": "1001"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1003-1003", "from_": "1003", "to_": "1003"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2001-2001", "from_": "2001", "to_": "2001"} + } + } + ] + } + }, + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-EVEN", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "EVEN", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-EVEN", + "tnMaintMaintPName": "EVEN" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk102-102", "from_": "102", "to_": "102"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk112-112", "from_": "112", "to_": "112"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk122-122", "from_": "122", "to_": "122"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk202-202", "from_": "202", "to_": "202"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1002-1002", "from_": "1002", "to_": "1002"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1004-1004", "from_": "1004", "to_": "1004"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2002-2002", "from_": "2002", "to_": "2002"} + } + } + ] + } + } +] diff --git a/tests/checks/switch_group_guideline_check/maintMaintGrp_SPINE_LEAF.json b/tests/checks/switch_group_guideline_check/maintMaintGrp_SPINE_LEAF.json new file mode 100644 index 00000000..f07b60e3 --- /dev/null +++ b/tests/checks/switch_group_guideline_check/maintMaintGrp_SPINE_LEAF.json @@ -0,0 +1,116 @@ +[ + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-SPINE", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "SPINE", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-SPINE", + "tnMaintMaintPName": "SPINE" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1001-1001", "from_": "1001", "to_": "1001"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1002-1002", "from_": "1002", "to_": "1002"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1003-1003", "from_": "1003", "to_": "1003"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk1004-1004", "from_": "1004", "to_": "1004"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2001-2001", "from_": "2001", "to_": "2001"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk2002-2002", "from_": "2002", "to_": "2002"} + } + } + ] + } + }, + { + "maintMaintGrp": { + "attributes": { + "dn": "uni/fabric/maintgrp-LEAF", + "fwtype": "switch", + "monPolDn": "uni/fabric/monfab-default", + "name": "LEAF", + "type": "range" + }, + "children": [ + { + "maintRsMgrpp": { + "attributes": { + "tCl": "maintMaintP", + "tDn": "uni/fabric/maintpol-LEAF", + "tnMaintMaintPName": "LEAF" + } + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk101-101", "from_": "101", "to_": "101"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk102-102", "from_": "102", "to_": "102"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk111-111", "from_": "111", "to_": "111"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk112-112", "from_": "112", "to_": "112"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk121-121", "from_": "121", "to_": "121"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk122-122", "from_": "122", "to_": "122"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk201-201", "from_": "201", "to_": "201"} + } + }, + { + "fabricNodeBlk": { + "attributes": {"name": "blk202-202", "from_": "202", "to_": "202"} + } + } + ] + } + } +] diff --git a/tests/checks/switch_group_guideline_check/test_switch_group_guideline_check.py b/tests/checks/switch_group_guideline_check/test_switch_group_guideline_check.py new file mode 100644 index 00000000..332e85f2 --- /dev/null +++ b/tests/checks/switch_group_guideline_check/test_switch_group_guideline_check.py @@ -0,0 +1,193 @@ +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__)) + +test_function = "switch_group_guideline_check" + +# icurl queries +upgrade_grp = "maintMaintGrp.json?rsp-subtree=children" +bgp_rr = "bgpRRNodePEp.json" +ipn_spines = 'l3extRsNodeL3OutAtt.json?query-target-filter=wcard(l3extRsNodeL3OutAtt.dn,"tn-infra/")' +apic_lldp = "lldpCtrlrAdjEp.json" +vpc_pairs = "fabricExplicitGEp.json?rsp-subtree=children&rsp-subtree-class=fabricNodePEp" + + +@pytest.mark.parametrize( + "icurl_outputs, fabric_nodes, expected_result, expected_data", + [ + # PASS + # Upgrade Grp: EVEN and ODD + # Spines: + # [Pod 1] 1001-1004, RR: 1001,1002, IPN 1001,1002 + # [Pod 2] 2001-2002, RR: 2001,2002, IPN 2001,2002 + # APIC Leaves: + # [Pod 1] 101-102 (APIC 1, 2) + # [Pod 2] 201-202 (APIC 3) + # VPC Leaves: + # [Pod 1] 101-102, 111-112 + # [Pod 2] 201-202 + ( + { + upgrade_grp: read_data(dir, "maintMaintGrp_EVEN_ODD.json"), + bgp_rr: read_data(dir, "bgpRRNodePEp_1001_1002_2001_2002.json"), + ipn_spines: read_data(dir, "l3extRsNodeL3OutAtt_1001_1002_2001_2002.json"), + apic_lldp: read_data(dir, "lldpCtrlrAdjEp.json"), + vpc_pairs: read_data(dir, "fabricExplicitGEp.json"), + }, + read_data(dir, "fabricNode.json"), + script.PASS, + [], + ), + # FAIL - All HA broken + # Upgrade Grp: all in one + # Spines: + # [Pod 1] 1001-1004, RR: 1001,1002, IPN 1001,1002 + # [Pod 2] 2001-2002, RR: 2001,2002, IPN 2001,2002 + # APIC Leaves: + # [Pod 1] 101-102 (APIC 1, 2) + # [Pod 2] 201-202 (APIC 3) + # VPC Leaves: + # [Pod 1] 101-102, 111-112 + # [Pod 2] 201-202 + ( + { + upgrade_grp: read_data(dir, "maintMaintGrp_ALL.json"), + bgp_rr: read_data(dir, "bgpRRNodePEp_1001_1002_2001_2002.json"), + ipn_spines: read_data(dir, "l3extRsNodeL3OutAtt_1001_1002_2001_2002.json"), + apic_lldp: read_data(dir, "lldpCtrlrAdjEp.json"), + vpc_pairs: read_data(dir, "fabricExplicitGEp.json"), + }, + read_data(dir, "fabricNode.json"), + script.FAIL_O, + [ + ["ALL", "1", "1001,1002,1003,1004", "All spine nodes in this pod are in the same group."], + ["ALL", "2", "2001,2002", "All spine nodes in this pod are in the same group."], + ["ALL", "1", "1001,1002", "All RR spine nodes in this pod are in the same group."], + ["ALL", "2", "2001,2002", "All RR spine nodes in this pod are in the same group."], + ["ALL", "1", "1001,1002", "All IPN/ISN spine nodes in this pod are in the same group."], + ["ALL", "2", "2001,2002", "All IPN/ISN spine nodes in this pod are in the same group."], + ["ALL", "1", "101,102", "All leaf nodes connected to APIC 1 are in the same group."], + ["ALL", "1", "101,102", "All leaf nodes connected to APIC 2 are in the same group."], + ["ALL", "2", "201,202", "All leaf nodes connected to APIC 3 are in the same group."], + ["ALL", "1", "101,102", "Both leaf nodes in the same vPC pair are in the same group."], + ["ALL", "1", "111,112", "Both leaf nodes in the same vPC pair are in the same group."], + ["ALL", "2", "201,202", "Both leaf nodes in the same vPC pair are in the same group."], + ], + ), + # FAIL - All HA broken + # Upgrade Grp: leaves in one group and spines in another + # Spines: + # [Pod 1] 1001-1004, RR: 1001,1002, IPN 1001,1002 + # [Pod 2] 2001-2002, RR: 2001,2002, IPN 2001,2002 + # APIC Leaves: + # [Pod 1] 101-102 (APIC 1, 2) + # [Pod 2] 201-202 (APIC 3) + # VPC Leaves: + # [Pod 1] 101-102, 111-112 + # [Pod 2] 201-202 + ( + { + upgrade_grp: read_data(dir, "maintMaintGrp_SPINE_LEAF.json"), + bgp_rr: read_data(dir, "bgpRRNodePEp_1001_1002_2001_2002.json"), + ipn_spines: read_data(dir, "l3extRsNodeL3OutAtt_1001_1002_2001_2002.json"), + apic_lldp: read_data(dir, "lldpCtrlrAdjEp.json"), + vpc_pairs: read_data(dir, "fabricExplicitGEp.json"), + }, + read_data(dir, "fabricNode.json"), + script.FAIL_O, + [ + ["SPINE", "1", "1001,1002,1003,1004", "All spine nodes in this pod are in the same group."], + ["SPINE", "2", "2001,2002", "All spine nodes in this pod are in the same group."], + ["SPINE", "1", "1001,1002", "All RR spine nodes in this pod are in the same group."], + ["SPINE", "2", "2001,2002", "All RR spine nodes in this pod are in the same group."], + ["SPINE", "1", "1001,1002", "All IPN/ISN spine nodes in this pod are in the same group."], + ["SPINE", "2", "2001,2002", "All IPN/ISN spine nodes in this pod are in the same group."], + ["LEAF", "1", "101,102", "All leaf nodes connected to APIC 1 are in the same group."], + ["LEAF", "1", "101,102", "All leaf nodes connected to APIC 2 are in the same group."], + ["LEAF", "2", "201,202", "All leaf nodes connected to APIC 3 are in the same group."], + ["LEAF", "1", "101,102", "Both leaf nodes in the same vPC pair are in the same group."], + ["LEAF", "1", "111,112", "Both leaf nodes in the same vPC pair are in the same group."], + ["LEAF", "2", "201,202", "Both leaf nodes in the same vPC pair are in the same group."], + ], + ), + # FAIL - All HA except for pod1 spine broken + # Upgrade Grp: + # GRP1: 101,102,121,122,1001,1002 + # GRP2: 111,112,201,202,1003,1004,2001,2003 + # Spines: + # [Pod 1] 1001-1004, RR: 1001,1002, IPN 1001,1002 + # [Pod 2] 2001-2002, RR: 2001,2002, IPN 2001,2002 + # APIC Leaves: + # [Pod 1] 101-102 (APIC 1, 2) + # [Pod 2] 201-202 (APIC 3) + # VPC Leaves: + # [Pod 1] 101-102, 111-112 + # [Pod 2] 201-202 + ( + { + upgrade_grp: read_data(dir, "maintMaintGrp_BAD_GRP1_GRP2.json"), + bgp_rr: read_data(dir, "bgpRRNodePEp_1001_1002_2001_2002.json"), + ipn_spines: read_data(dir, "l3extRsNodeL3OutAtt_1001_1002_2001_2002.json"), + apic_lldp: read_data(dir, "lldpCtrlrAdjEp.json"), + vpc_pairs: read_data(dir, "fabricExplicitGEp.json"), + }, + read_data(dir, "fabricNode.json"), + script.FAIL_O, + [ + ["GRP1", "1", "1001,1002", "All RR spine nodes in this pod are in the same group."], + ["GRP1", "1", "1001,1002", "All IPN/ISN spine nodes in this pod are in the same group."], + ["GRP1", "1", "101,102", "All leaf nodes connected to APIC 1 are in the same group."], + ["GRP1", "1", "101,102", "All leaf nodes connected to APIC 2 are in the same group."], + ["GRP1", "1", "101,102", "Both leaf nodes in the same vPC pair are in the same group."], + ["GRP2", "2", "2001,2002", "All spine nodes in this pod are in the same group."], + ["GRP2", "2", "2001,2002", "All RR spine nodes in this pod are in the same group."], + ["GRP2", "2", "2001,2002", "All IPN/ISN spine nodes in this pod are in the same group."], + ["GRP2", "2", "201,202", "All leaf nodes connected to APIC 3 are in the same group."], + ["GRP2", "1", "111,112", "Both leaf nodes in the same vPC pair are in the same group."], + ["GRP2", "2", "201,202", "Both leaf nodes in the same vPC pair are in the same group."], + ], + ), + # FAIL - Only pod1 spine RR HA is broken + # Upgrade Grp: + # SPINE_GRP1: 1001-1003, 2001 + # SPINE_GRP2: 1004, 2002 + # EVEN: even leaves + # ODD: odd leaves + # Spines: + # [Pod 1] 1001-1004, RR: 1001,1002, IPN 1003,1004 + # [Pod 2] 2001-2002, RR: 2001,2002, IPN 2001,2002 + # APIC Leaves: + # [Pod 1] 101-102 (APIC 1, 2) + # [Pod 2] 201-202 (APIC 3) + # VPC Leaves: + # [Pod 1] 101-102, 111-112 + # [Pod 2] 201-202 + ( + { + upgrade_grp: read_data(dir, "maintMaintGrp_BAD_ONLY_POD1_SPINE_RR.json"), + bgp_rr: read_data(dir, "bgpRRNodePEp_1001_1002_2001_2002.json"), + ipn_spines: read_data(dir, "l3extRsNodeL3OutAtt_1003_1004_2001_2002.json"), + apic_lldp: read_data(dir, "lldpCtrlrAdjEp.json"), + vpc_pairs: read_data(dir, "fabricExplicitGEp.json"), + }, + read_data(dir, "fabricNode.json"), + script.FAIL_O, + [ + ["SPINE_GRP1", "1", "1001,1002", "All RR spine nodes in this pod are in the same group."], + ], + ), + ], +) +def test_logic(run_check, mock_icurl, fabric_nodes, expected_result, expected_data): + result = run_check( + fabric_nodes=fabric_nodes, + ) + assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/switch_status_check/fabricNode_NEG.json b/tests/checks/switch_status_check/fabricNode_NEG.json new file mode 100644 index 00000000..3dffa268 --- /dev/null +++ b/tests/checks/switch_status_check/fabricNode_NEG.json @@ -0,0 +1,122 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "name": "apic1", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "name": "LF101", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "name": "LF102", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-103", + "fabricSt": "active", + "id": "103", + "name": "LF103", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-111", + "fabricSt": "active", + "id": "111", + "name": "T2_LF111", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-112", + "fabricSt": "active", + "id": "112", + "name": "T2_LF112", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-121", + "fabricSt": "active", + "id": "121", + "name": "RL_LF121", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-122", + "fabricSt": "active", + "id": "122", + "name": "RL_LF122", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1001", + "fabricSt": "active", + "id": "1001", + "name": "SP1001", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1002", + "fabricSt": "active", + "id": "1002", + "name": "SP1002", + "role": "spine", + "nodeType": "unspecified" + } + } + } +] diff --git a/tests/checks/switch_status_check/fabricNode_POS.json b/tests/checks/switch_status_check/fabricNode_POS.json new file mode 100644 index 00000000..1a009a17 --- /dev/null +++ b/tests/checks/switch_status_check/fabricNode_POS.json @@ -0,0 +1,122 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "name": "apic1", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "name": "LF101", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "name": "LF102", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-103", + "fabricSt": "inactive", + "id": "103", + "name": "LF103", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-111", + "fabricSt": "active", + "id": "111", + "name": "T2_LF111", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-112", + "fabricSt": "inactive", + "id": "112", + "name": "T2_LF112", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-121", + "fabricSt": "inactive", + "id": "121", + "name": "RL_LF121", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-122", + "fabricSt": "active", + "id": "122", + "name": "RL_LF122", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1001", + "fabricSt": "active", + "id": "1001", + "name": "SP1001", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1002", + "fabricSt": "active", + "id": "1002", + "name": "SP1002", + "role": "spine", + "nodeType": "unspecified" + } + } + } +] diff --git a/tests/checks/switch_status_check/fabricRsDecommissionNode.json b/tests/checks/switch_status_check/fabricRsDecommissionNode.json new file mode 100644 index 00000000..9fb2e770 --- /dev/null +++ b/tests/checks/switch_status_check/fabricRsDecommissionNode.json @@ -0,0 +1,12 @@ +[ + { + "fabricRsDecommissionNode": { + "attributes": { + "dn": "uni/fabric/outofsvc/rsdecommissionNode-[topology/pod-1/node-112]", + "debug": "yes", + "tDn": "topology/pod-1/node-112", + "targetId": "112" + } + } + } +] diff --git a/tests/checks/switch_status_check/test_switch_status_check.py b/tests/checks/switch_status_check/test_switch_status_check.py new file mode 100644 index 00000000..e16f5012 --- /dev/null +++ b/tests/checks/switch_status_check/test_switch_status_check.py @@ -0,0 +1,46 @@ +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__)) + +test_function = "switch_status_check" + +# icurl queries +gir_nodes = 'fabricRsDecommissionNode.json?&query-target-filter=eq(fabricRsDecommissionNode.debug,"yes")' + + +@pytest.mark.parametrize( + "icurl_outputs, fabric_nodes, expected_result, expected_data", + [ + # FAIL - Some switches are not active + ( + {gir_nodes: read_data(dir, "fabricRsDecommissionNode.json")}, + read_data(dir, "fabricNode_POS.json"), + script.FAIL_UF, + [ + ["1", "103", "inactive"], + ["1", "112", "inactive (Maintenance)"], + ["1", "121", "inactive"], + ], + ), + # PASS - All switches are active + ( + {gir_nodes: []}, + read_data(dir, "fabricNode_NEG.json"), + script.PASS, + [], + ), + ], +) +def test_logic(run_check, mock_icurl, fabric_nodes, expected_result, expected_data): + result = run_check( + fabric_nodes=fabric_nodes, + ) + assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/vpc_paired_switches_check/fabricNode.json b/tests/checks/vpc_paired_switches_check/fabricNode.json new file mode 100644 index 00000000..73e7d5b8 --- /dev/null +++ b/tests/checks/vpc_paired_switches_check/fabricNode.json @@ -0,0 +1,145 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "id": "1", + "name": "apic1", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-2", + "id": "2", + "name": "apic2", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-3", + "id": "3", + "name": "apic3", + "role": "controller", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "id": "101", + "name": "LF101", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-102", + "id": "102", + "name": "LF102", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-103", + "id": "103", + "name": "LF103", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-104", + "id": "104", + "name": "LF104", + "role": "leaf", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-111", + "id": "111", + "name": "T2_LF111", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-112", + "id": "112", + "name": "T2_LF112", + "role": "leaf", + "nodeType": "tier-2-leaf" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-121", + "id": "121", + "name": "RL_LF121", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-122", + "id": "122", + "name": "RL_LF122", + "role": "leaf", + "nodeType": "remote-leaf-wan" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1001", + "id": "1001", + "name": "SP1001", + "role": "spine", + "nodeType": "unspecified" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1002", + "id": "1002", + "name": "SP1002", + "role": "spine", + "nodeType": "unspecified" + } + } + } +] diff --git a/tests/checks/vpc_paired_switches_check/test_vpc_paired_switches_check.py b/tests/checks/vpc_paired_switches_check/test_vpc_paired_switches_check.py index 2b827248..c6bb8e70 100644 --- a/tests/checks/vpc_paired_switches_check/test_vpc_paired_switches_check.py +++ b/tests/checks/vpc_paired_switches_check/test_vpc_paired_switches_check.py @@ -11,27 +11,28 @@ test_function = "vpc_paired_switches_check" -# icurl queries -topSystems = "topSystem.json" - @pytest.mark.parametrize( - "icurl_outputs, vpc_node_ids, expected_result", + "vpc_node_ids, expected_result, expected_data", [ # all leaf switches are in vPC ( - {topSystems: read_data(dir, "topSystem.json")}, - ["101", "102", "103", "204", "206"], + ["101", "102", "103", "104", "111", "112", "121", "122"], script.PASS, + [], ), # not all leaf switches are in vPC ( - {topSystems: read_data(dir, "topSystem.json")}, - ["101", "103", "204", "206"], + ["101", "102", "111", "112"], script.MANUAL, + [["103", "LF103"], ["104", "LF104"], ["121", "RL_LF121"], ["122", "RL_LF122"]], ), ], ) -def test_logic(run_check, mock_icurl, vpc_node_ids, expected_result): - result = run_check(vpc_node_ids=vpc_node_ids) +def test_logic(run_check, vpc_node_ids, expected_result, expected_data): + result = run_check( + vpc_node_ids=vpc_node_ids, + fabric_nodes=read_data(dir, "fabricNode.json"), + ) assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/vpc_paired_switches_check/topSystem.json b/tests/checks/vpc_paired_switches_check/topSystem.json deleted file mode 100644 index 39faf83a..00000000 --- a/tests/checks/vpc_paired_switches_check/topSystem.json +++ /dev/null @@ -1,134 +0,0 @@ -[ - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-1/sys", - "id": "1", - "name": "apic1", - "podId": "1", - "role": "controller" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-2/node-3/sys", - "id": "3", - "name": "apic3", - "podId": "2", - "role": "controller" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-2/sys", - "id": "2", - "name": "apic2", - "podId": "1", - "role": "controller" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-101/sys", - "id": "101", - "name": "leaf101", - "podId": "1", - "role": "leaf" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-102/sys", - "id": "102", - "name": "leaf102", - "podId": "1", - "role": "leaf" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-103/sys", - "id": "103", - "name": "leaf103", - "podId": "1", - "role": "leaf" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-2/node-204/sys", - "id": "204", - "name": "leaf204", - "podId": "2", - "role": "leaf" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-2/node-206/sys", - "id": "206", - "name": "leaf206", - "podId": "2", - "role": "leaf" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-2/node-2002/sys", - "id": "2002", - "name": "spine2002", - "podId": "2", - "role": "spine" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-2/node-2001/sys", - "id": "2001", - "name": "spine2001", - "podId": "2", - "role": "spine" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-1002/sys", - "id": "1002", - "name": "spine1002", - "podId": "1", - "role": "spine" - } - } - }, - { - "topSystem": { - "attributes": { - "dn": "topology/pod-1/node-1001/sys", - "id": "1001", - "name": "spine1001", - "podId": "1", - "role": "spine" - } - } - } -] diff --git a/tests/conftest.py b/tests/conftest.py index 750796bd..aabe0120 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -131,6 +131,136 @@ def expected_common_data(request): "tversion": script.AciVersion("6.2(1a)"), "sw_cversion": script.AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"], + "fabric_nodes": [ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller", + "version": "6.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller", + "version": "6.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller", + "version": "6.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf", + "version": "n9000-16.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf", + "version": "n9000-16.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.111", + "dn": "topology/pod-1/node-1001", + "fabricSt": "active", + "id": "1001", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine1001", + "nodeType": "unspecified", + "role": "spine", + "version": "n9000-16.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-2/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C93180YC-FX3", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf201", + "nodeType": "unspecified", + "role": "leaf", + "version": "n9000-16.0(9d)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.211", + "dn": "topology/pod-2/node-2001", + "fabricSt": "active", + "id": "2001", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine2001", + "nodeType": "unspecified", + "role": "spine", + "version": "n9000-16.1(1a)", + } + } + }, + ], } param = getattr(request, "param", {}) for key in data: @@ -332,6 +462,7 @@ def _check(**kwargs): _check.__name__ = check_id # Set the function name for the check return _check + return _check_factory diff --git a/tests/test_common_data.py b/tests/test_common_data.py index e130f506..49129185 100644 --- a/tests/test_common_data.py +++ b/tests/test_common_data.py @@ -2,6 +2,7 @@ import importlib import logging import json +import sys script = importlib.import_module("aci-preupgrade-validation-script") AciVersion = script.AciVersion @@ -34,32 +35,150 @@ def mock_get_target_version(monkeypatch): def _mock_get_target_version(arg_tversion): if arg_tversion: + script.prints("Target APIC version is overridden to %s" % arg_tversion) try: - return AciVersion(arg_tversion) + target_version = AciVersion(arg_tversion) except ValueError as e: script.prints(e) - raise SystemExit(1) + sys.exit(1) + return target_version return AciVersion("6.2(1a)") monkeypatch.setattr(script, "get_target_version", _mock_get_target_version) -outputs = { - "cversion": [ +_icurl_outputs = { + "fabricNode.json": [ { - "firmwareCtrlrRunning": { + "fabricNode": { "attributes": { - "dn": "topology/pod-1/node-1/sys/ctrlrfwstatuscont/ctrlrrunning", + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller", "version": "6.1(1a)", } } - } - ], - "switch_version": [ - {"firmwareRunning": {"attributes": {"peVer": "6.1(1a)", "version": "n9000-16.1(1a)"}}}, - {"firmwareRunning": {"attributes": {"peVer": "6.0(9d)", "version": "n9000-16.0(9d)"}}}, + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller", + "version": "6.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller", + "version": "6.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf", + "version": "n9000-16.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf", + "version": "n9000-16.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.111", + "dn": "topology/pod-1/node-1001", + "fabricSt": "active", + "id": "1001", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine1001", + "nodeType": "unspecified", + "role": "spine", + "version": "n9000-16.1(1a)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-2/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C93180YC-FX3", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf201", + "nodeType": "unspecified", + "role": "leaf", + "version": "n9000-16.0(9d)", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.211", + "dn": "topology/pod-2/node-2001", + "fabricSt": "active", + "id": "2001", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine2001", + "nodeType": "unspecified", + "role": "spine", + "version": "n9000-16.1(1a)", + } + } + }, ], - "vpc_nodes": [ + "fabricNodePEp.json": [ {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-102/nodepep-101", "id": "101"}}}, {"fabricNodePEp": {"attributes": {"dn": "uni/fabric/protpol/expgep-101-102/nodepep-102", "id": "102"}}}, ], @@ -68,20 +187,6 @@ def _mock_get_target_version(arg_tversion): output_error = [{"error": {"attributes": {"code": "400", "text": "Request failed, unresolved class for dummyClass"}}}] -@pytest.fixture(scope="function") -def icurl_outputs(request): - param = getattr(request, "param", {}) - data = { - "firmwareCtrlrRunning.json": outputs["cversion"], - "firmwareRunning.json": outputs["switch_version"], - "fabricNodePEp.json": outputs["vpc_nodes"], - } - for key in data: - if param.get(key, "non_falsy_default") != "non_falsy_default": - data[key] = param[key] - return data - - @pytest.fixture(scope="function") def fake_args(request): data = { @@ -89,6 +194,7 @@ def fake_args(request): "cversion": None, "tversion": None, } + # update data contents when parametrize provides non-falsy values for key in data: if request.param.get(key, "non_falsy_default") != "non_falsy_default": data[key] = request.param[key] @@ -101,10 +207,11 @@ def fake_args(request): @pytest.mark.parametrize( - "fake_args, expected_common_data", + "icurl_outputs, fake_args, expected_common_data", [ # Default, no argparse arguments pytest.param( + _icurl_outputs, {}, {}, id="default_no_args", @@ -112,6 +219,7 @@ def fake_args(request): # `api_only` is True. # No `get_credentials()`, no username nor password pytest.param( + _icurl_outputs, { "api_only": True, }, @@ -120,6 +228,7 @@ def fake_args(request): ), # `arg_tversion` is provided (i.e. -t 6.1(4a)) pytest.param( + _icurl_outputs, { "tversion": "6.1(4a)", }, @@ -130,48 +239,56 @@ def fake_args(request): ), # `arg_tversion` and `arg_cversion` are both provided (i.e. -t 6.1(4a)) pytest.param( + _icurl_outputs, { "cversion": "6.0(8d)", "tversion": "6.1(4a)", }, { "cversion": AciVersion("6.0(8d)"), + "sw_cversion": AciVersion("6.0(8d)"), "tversion": AciVersion("6.1(4a)"), }, id="cversion_tversion", ), # versions are switch syntax pytest.param( + _icurl_outputs, { "cversion": "16.0(4d)", "tversion": "16.1(4a)", }, { "cversion": AciVersion("6.0(4d)"), + "sw_cversion": AciVersion("6.0(4d)"), "tversion": AciVersion("6.1(4a)"), }, id="cversion_tversion_with_switch_version_syntax", ), # versions are APIC image name syntax pytest.param( + _icurl_outputs, { "cversion": "aci-apic-dk9.6.0.1a.bin", "tversion": "aci-apic-dk9.6.2.1a.bin", }, { "cversion": AciVersion("6.0(1a)"), + "sw_cversion": AciVersion("6.0(1a)"), "tversion": AciVersion("6.2(1a)"), }, id="cversion_tversion_with_apic_image_name_syntax", ), # versions are Switch image name syntax pytest.param( + _icurl_outputs, { "cversion": "n9000-16.0(1a).bin", "tversion": "n9000-16.2(1a).bin", }, { "cversion": AciVersion("6.0(1a)"), + "sw_cversion": AciVersion("6.0(1a)"), "tversion": AciVersion("6.2(1a)"), }, id="cversion_tversion_with_switch_image_name_syntax", @@ -179,7 +296,7 @@ def fake_args(request): ], indirect=["fake_args", "expected_common_data"], ) -def test_common_data(mock_icurl, icurl_outputs, fake_args, expected_common_data): +def test_common_data(mock_icurl, fake_args, expected_common_data): """test query_common_data and write_script_metadata""" # --- test for `query_common_data()` common_data = script.query_common_data( @@ -209,35 +326,59 @@ def test_common_data(mock_icurl, icurl_outputs, fake_args, expected_common_data) assert meta["total_checks"] == 100 -def test_tversion_invald(): +@pytest.mark.parametrize("icurl_outputs", [_icurl_outputs]) +def test_tversion_invald(capsys, mock_icurl): with pytest.raises(SystemExit): - with pytest.raises(ValueError): - script.query_common_data(arg_cversion="6.0(1a)", arg_tversion="invalid_version") + script.query_common_data(arg_cversion="6.0(1a)", arg_tversion="invalid_version") + + captured = capsys.readouterr() + expected_output = """\ +Gathering Node Information... + +Current version is overridden to 6.0(1a) +Target APIC version is overridden to invalid_version +Parsing failure of ACI version `invalid_version` +""" + assert captured.out.endswith(expected_output), "captured.out is:\n{}".format(captured.out) -def test_cversion_invald(): +@pytest.mark.parametrize("icurl_outputs", [_icurl_outputs]) +def test_cversion_invald(capsys, mock_icurl): with pytest.raises(SystemExit): - with pytest.raises(ValueError): - script.query_common_data(arg_cversion="invalid_version", arg_tversion="6.0(1a)") + script.query_common_data(arg_cversion="invalid_version", arg_tversion="6.0(1a)") + + captured = capsys.readouterr() + expected_output = """\ +Gathering Node Information... + +Current version is overridden to invalid_version +Parsing failure of ACI version `invalid_version` +""" + assert captured.out.endswith(expected_output), "captured.out is:\n{}".format(captured.out) @pytest.mark.parametrize( "icurl_outputs, print_output", [ - # `get_cversion()` failure - ({"firmwareCtrlrRunning.json": output_error}, "Checking current APIC version..."), - # `get_switch_version()` failure - ({"firmwareRunning.json": output_error}, "Gathering Lowest Switch Version from Firmware Repository..."), + # `get_fabric_nodes()` failure + ( + { + "fabricNode.json": output_error, + "fabricNodePEp.json": _icurl_outputs["fabricNodePEp.json"], + }, + "Gathering Node Information...\n\n", + ), # `get_vpc_nodes()` failure - ({"fabricNodePEp.json": output_error}, "Collecting VPC Node IDs..."), + ( + {"fabricNode.json": _icurl_outputs["fabricNode.json"], "fabricNodePEp.json": output_error}, + "Collecting VPC Node IDs...", + ), ], - indirect=["icurl_outputs"], ) def test_icurl_failure_in_query_common_data(capsys, caplog, mock_icurl, print_output): caplog.set_level(logging.CRITICAL) with pytest.raises(SystemExit): - with pytest.raises(Exception): - script.query_common_data() + script.query_common_data() captured = capsys.readouterr() expected_output = ( print_output From cbf9730958c9dd585c86ea0286a8449afc2983df Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Mon, 17 Nov 2025 00:25:55 -0800 Subject: [PATCH 07/16] fix: fallback on firmware queries on older ACI version to get current ver --- aci-preupgrade-validation-script.py | 24 ++- tests/test_common_data.py | 250 +++++++++++++++++++++++++++- 2 files changed, 267 insertions(+), 7 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 295169f9..0df1a46e 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1697,19 +1697,39 @@ def get_current_versions(fabric_nodes, arg_cversion): sys.exit(1) return current_version, current_version + apic1_dn = "" apic_version = "" # There can be only one APIC version switch_versions = set() for node in fabric_nodes: version = node["fabricNode"]["attributes"]["version"] - if not version: + if not version: # inactive nodes show empty version continue if node["fabricNode"]["attributes"]["role"] == "controller": - apic_version = AciVersion(version) + apic_version = version + if node["fabricNode"]["attributes"]["id"] == "1": + apic1_dn = node["fabricNode"]["attributes"]["dn"] else: switch_versions.add(version) + # fabricNode.version in older versions like 3.2 shows an invalid version like "A" + # which cannot be parsed by AciVersion. In that case, query the firmware class. + is_old_version = False + try: + apic_version = AciVersion(apic_version) + except ValueError: + is_old_version = True + apic1_firmware = icurl('mo', apic1_dn + "/sys/ctrlrfwstatuscont/ctrlrrunning.json") + apic1_version = apic1_firmware[0]['firmwareCtrlrRunning']['attributes']['version'] + apic_version = AciVersion(apic1_version) + prints("Current APIC Version...{}".format(apic_version)) + if is_old_version: + prints("Checking Switch Version ...") + firmwares = icurl('class', 'firmwareRunning.json') + for firmware in firmwares: + switch_versions.add(firmware['firmwareRunning']['attributes']['peVer']) + msg = "Lowest Switch Version...{}" if not switch_versions: prints(msg.format("Not Found! Join switches to the fabric then re-run this script.\n")) diff --git a/tests/test_common_data.py b/tests/test_common_data.py index 49129185..0f678ca7 100644 --- a/tests/test_common_data.py +++ b/tests/test_common_data.py @@ -184,7 +184,205 @@ def _mock_get_target_version(arg_tversion): ], } -output_error = [{"error": {"attributes": {"code": "400", "text": "Request failed, unresolved class for dummyClass"}}}] +_icurl_outputs_old = { + # fabricNode.version in older versions like 3.2 shows an invalid version like "A" + # for controller and empty for active switches. + "fabricNode.json": [ + { + "fabricNode": { + "attributes": { + "address": "10.0.0.1", + "dn": "topology/pod-1/node-1", + "fabricSt": "commissioned", + "id": "1", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic1", + "nodeType": "unspecified", + "role": "controller", + "version": "A", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.2", + "dn": "topology/pod-1/node-2", + "fabricSt": "commissioned", + "id": "2", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic2", + "nodeType": "unspecified", + "role": "controller", + "version": "A", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.3", + "dn": "topology/pod-2/node-3", + "fabricSt": "commissioned", + "id": "3", + "model": "APIC-SERVER-L2", + "monPolDn": "uni/fabric/monfab-default", + "name": "apic3", + "nodeType": "unspecified", + "role": "controller", + "version": "A", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.101", + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "id": "101", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf101", + "nodeType": "unspecified", + "role": "leaf", + "version": "", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.102", + "dn": "topology/pod-1/node-102", + "fabricSt": "active", + "id": "102", + "model": "N9K-C93180YC-FX", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf102", + "nodeType": "unspecified", + "role": "leaf", + "version": "", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.111", + "dn": "topology/pod-1/node-1001", + "fabricSt": "active", + "id": "1001", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine1001", + "nodeType": "unspecified", + "role": "spine", + "version": "", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.201", + "dn": "topology/pod-2/node-201", + "fabricSt": "active", + "id": "201", + "model": "N9K-C93180YC-FX3", + "monPolDn": "uni/fabric/monfab-default", + "name": "leaf201", + "nodeType": "unspecified", + "role": "leaf", + "version": "", + } + } + }, + { + "fabricNode": { + "attributes": { + "address": "10.0.0.211", + "dn": "topology/pod-2/node-2001", + "fabricSt": "active", + "id": "2001", + "model": "N9K-C9504", + "monPolDn": "uni/fabric/monfab-default", + "name": "spine2001", + "nodeType": "unspecified", + "role": "spine", + "version": "", + } + } + }, + ], + "fabricNodePEp.json": _icurl_outputs["fabricNodePEp.json"], + "topology/pod-1/node-1/sys/ctrlrfwstatuscont/ctrlrrunning.json": [ + { + "firmwareCtrlrRunning": { + "attributes": { + "dn": "topology/pod-1/node-1/sys/ctrlrfwstatuscont/ctrlrrunning", + "type": "controller", + "version": "3.2(7f)" + } + } + } + ], + "firmwareRunning.json": [ + { + "firmwareRunning": { + "attributes": { + "dn": "topology/pod-1/node-101/sys/fwstatuscont/running", + "peVer": "3.1(2u)", + "type": "switch", + "version": "n9000-13.1(2u)" + } + } + }, + { + "firmwareRunning": { + "attributes": { + "dn": "topology/pod-1/node-102/sys/fwstatuscont/running", + "peVer": "3.2(7f)", + "type": "switch", + "version": "n9000-13.2(7f)" + } + } + }, + { + "firmwareRunning": { + "attributes": { + "dn": "topology/pod-1/node-1001/sys/fwstatuscont/running", + "peVer": "3.2(7f)", + "type": "switch", + "version": "n9000-13.2(7f)" + } + } + }, + { + "firmwareRunning": { + "attributes": { + "dn": "topology/pod-2/node-201/sys/fwstatuscont/running", + "peVer": "3.2(7f)", + "type": "switch", + "version": "n9000-13.2(7f)" + } + } + }, + { + "firmwareRunning": { + "attributes": { + "dn": "topology/pod-2/node-2001/sys/fwstatuscont/running", + "peVer": "3.2(7f)", + "type": "switch", + "version": "n9000-13.2(7f)" + } + } + }, + + ], +} @pytest.fixture(scope="function") @@ -213,9 +411,28 @@ def fake_args(request): pytest.param( _icurl_outputs, {}, - {}, + { + "cversion": AciVersion("6.1(1a)"), + "sw_cversion": AciVersion("6.0(9d)"), + "tversion": AciVersion("6.2(1a)"), + "fabric_nodes": _icurl_outputs["fabricNode.json"], + "vpc_node_ids": ["101", "102"], + }, id="default_no_args", ), + # Default, no argparse arguments, old ACI version + pytest.param( + _icurl_outputs_old, + {}, + { + "cversion": AciVersion("3.2(7f)"), + "sw_cversion": AciVersion("3.1(2u)"), + "tversion": AciVersion("6.2(1a)"), + "fabric_nodes": _icurl_outputs_old["fabricNode.json"], + "vpc_node_ids": ["101", "102"], + }, + id="default_no_args_old_aci", + ), # `api_only` is True. # No `get_credentials()`, no username nor password pytest.param( @@ -223,7 +440,15 @@ def fake_args(request): { "api_only": True, }, - {"username": None, "password": None}, + { + "username": None, + "password": None, + "cversion": AciVersion("6.1(1a)"), + "sw_cversion": AciVersion("6.0(9d)"), + "tversion": AciVersion("6.2(1a)"), + "fabric_nodes": _icurl_outputs["fabricNode.json"], + "vpc_node_ids": ["101", "102"], + }, id="api_only", ), # `arg_tversion` is provided (i.e. -t 6.1(4a)) @@ -233,7 +458,11 @@ def fake_args(request): "tversion": "6.1(4a)", }, { + "cversion": AciVersion("6.1(1a)"), + "sw_cversion": AciVersion("6.0(9d)"), "tversion": AciVersion("6.1(4a)"), + "fabric_nodes": _icurl_outputs["fabricNode.json"], + "vpc_node_ids": ["101", "102"], }, id="tversion", ), @@ -248,6 +477,8 @@ def fake_args(request): "cversion": AciVersion("6.0(8d)"), "sw_cversion": AciVersion("6.0(8d)"), "tversion": AciVersion("6.1(4a)"), + "fabric_nodes": _icurl_outputs["fabricNode.json"], + "vpc_node_ids": ["101", "102"], }, id="cversion_tversion", ), @@ -262,6 +493,8 @@ def fake_args(request): "cversion": AciVersion("6.0(4d)"), "sw_cversion": AciVersion("6.0(4d)"), "tversion": AciVersion("6.1(4a)"), + "fabric_nodes": _icurl_outputs["fabricNode.json"], + "vpc_node_ids": ["101", "102"], }, id="cversion_tversion_with_switch_version_syntax", ), @@ -276,6 +509,8 @@ def fake_args(request): "cversion": AciVersion("6.0(1a)"), "sw_cversion": AciVersion("6.0(1a)"), "tversion": AciVersion("6.2(1a)"), + "fabric_nodes": _icurl_outputs["fabricNode.json"], + "vpc_node_ids": ["101", "102"], }, id="cversion_tversion_with_apic_image_name_syntax", ), @@ -290,6 +525,8 @@ def fake_args(request): "cversion": AciVersion("6.0(1a)"), "sw_cversion": AciVersion("6.0(1a)"), "tversion": AciVersion("6.2(1a)"), + "fabric_nodes": _icurl_outputs["fabricNode.json"], + "vpc_node_ids": ["101", "102"], }, id="cversion_tversion_with_switch_image_name_syntax", ), @@ -363,14 +600,17 @@ def test_cversion_invald(capsys, mock_icurl): # `get_fabric_nodes()` failure ( { - "fabricNode.json": output_error, + "fabricNode.json": [{"error": {"attributes": {"code": "400", "text": "Request failed, unresolved class for dummyClass"}}}], "fabricNodePEp.json": _icurl_outputs["fabricNodePEp.json"], }, "Gathering Node Information...\n\n", ), # `get_vpc_nodes()` failure ( - {"fabricNode.json": _icurl_outputs["fabricNode.json"], "fabricNodePEp.json": output_error}, + { + "fabricNode.json": _icurl_outputs["fabricNode.json"], + "fabricNodePEp.json": [{"error": {"attributes": {"code": "400", "text": "Request failed, unresolved class for dummyClass"}}}], + }, "Collecting VPC Node IDs...", ), ], From 1a86ed63cb0a17807cfcd1d50f932a0a9b9654ba Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Thu, 20 Nov 2025 23:58:01 -0800 Subject: [PATCH 08/16] fix: fallback to infraWiNode with pre-4.0 without fabricNode.address --- aci-preupgrade-validation-script.py | 113 +++++++++++------- .../checks/apic_ssd_check/fabricNode_old.json | 62 ++++++++++ .../apic_ssd_check/infraWiNode_apic1.json | 62 ++++++++++ .../apic_ssd_check/test_apic_ssd_check.py | 44 +++++-- .../fabricNode_old.json | 62 ++++++++++ .../infraWiNode_apic1.json | 62 ++++++++++ .../test_apic_version_md5_check.py | 72 +++++++---- .../fabricNode_old.json | 62 ++++++++++ .../infraWiNode_apic1.json | 62 ++++++++++ .../test_observer_db_size_check.py | 60 +++++++--- 10 files changed, 569 insertions(+), 92 deletions(-) create mode 100644 tests/checks/apic_ssd_check/fabricNode_old.json create mode 100644 tests/checks/apic_ssd_check/infraWiNode_apic1.json create mode 100644 tests/checks/apic_version_md5_check/fabricNode_old.json create mode 100644 tests/checks/apic_version_md5_check/infraWiNode_apic1.json create mode 100644 tests/checks/observer_db_size_check/fabricNode_old.json create mode 100644 tests/checks/observer_db_size_check/infraWiNode_apic1.json diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 0df1a46e..043cfd87 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -2604,7 +2604,7 @@ def switch_ssd_check(**kwargs): @check_wrapper(check_title="APIC SSD Health") def apic_ssd_check(cversion, username, password, fabric_nodes, **kwargs): result = FAIL_UF - headers = ["Pod", "Node", "Storage Unit", "% lifetime remaining", "Recommended Action"] + headers = ["APIC ID", "APIC Name", "Storage Unit", "% lifetime remaining", "Recommended Action"] data = [] unformatted_headers = ["Fault DN", "% lifetime remaining", "Recommended Action"] unformatted_data = [] @@ -2619,12 +2619,17 @@ def apic_ssd_check(cversion, username, password, fabric_nodes, **kwargs): for faultInst in faultInsts: code = faultInst["faultInst"]["attributes"]["code"] lifetime_remaining = threshold.get(code, "unknown") - dn_array = re.search(dn_regex, faultInst['faultInst']['attributes']['dn']) - if dn_array: + dn_match = re.search(dn_regex, faultInst['faultInst']['attributes']['dn']) + if dn_match: + apic_name = "-" + for node in fabric_nodes: + if node["fabricNode"]["attributes"]["id"] == dn_match.group("node"): + apic_name = node["fabricNode"]["attributes"]["name"] + break data.append([ - dn_array.group("pod"), - dn_array.group("node"), - dn_array.group("storage"), + dn_match.group("node"), + apic_name, + dn_match.group("storage"), lifetime_remaining, recommended_action, ]) @@ -2648,34 +2653,36 @@ def apic_ssd_check(cversion, username, password, fabric_nodes, **kwargs): apics = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["role"] == "controller"] if not apics: return Result(result=ERROR, msg="No fabricNode of APIC. Is the cluster healthy?", doc_url=doc_url) + # `fabricNode` in pre-4.0 does not have `address` + if not apics[0]["fabricNode"]["attributes"].get("address"): + apic1 = [apic for apic in apics if apic["fabricNode"]["attributes"]["id"] == "1"][0] + apic1_dn = apic1["fabricNode"]["attributes"]["dn"] + apics = icurl("class", "{}/infraWiNode.json".format(apic1_dn)) report_other = False - checked_apics = {} for apic in apics: - attr = apic['fabricNode']['attributes'] - if attr['address'] in checked_apics: continue - checked_apics[attr['address']] = 1 - dn = re.search(node_regex, attr['dn']) - if dn: - pod_id = dn.group('pod') - node_id = dn.group('node') + if apic.get("fabricNode"): + apic_id = apic["fabricNode"]["attributes"]["id"] + apic_name = apic["fabricNode"]["attributes"]["name"] + apic_addr = apic["fabricNode"]["attributes"]["address"] else: - pod_id = "--" - node_id = attr['id'] + apic_id = apic["infraWiNode"]["attributes"]["id"] + apic_name = apic["infraWiNode"]["attributes"]["nodeName"] + apic_addr = apic["infraWiNode"]["attributes"]["addr"] try: - c = Connection(attr['address']) + c = Connection(apic_addr) c.username = username c.password = password c.log = LOG_FILE c.connect() except Exception as e: - data.append([pod_id, node_id, '-', '-', str(e)]) + data.append([apic_id, apic_name, '-', '-', str(e)]) has_error = True continue try: c.cmd('grep -oE "SSD Wearout Indicator is [0-9]+" /var/log/dme/log/svc_ifc_ae.bin.log | tail -1') except Exception as e: - data.append([pod_id, node_id, '-', '-', str(e)]) + data.append([apic_id, apic_name, '-', '-', str(e)]) has_error = True continue @@ -2683,11 +2690,11 @@ def apic_ssd_check(cversion, username, password, fabric_nodes, **kwargs): if wearout_ind is not None: wearout = wearout_ind.group('wearout') if int(wearout) < 5: - data.append([pod_id, node_id, "Solid State Disk", wearout, recommended_action]) + data.append([apic_id, apic_name, "Solid State Disk", wearout, recommended_action]) report_other = True continue if report_other: - data.append([pod_id, node_id, "Solid State Disk", wearout, "No Action Required"]) + data.append([apic_id, apic_name, "Solid State Disk", wearout, "No Action Required"]) if has_error: result = ERROR elif not data and not unformatted_data: @@ -3271,7 +3278,7 @@ def lldp_with_infra_vlan_mismatch_check(**kwargs): @check_wrapper(check_title="APIC Target version image and MD5 hash") def apic_version_md5_check(tversion, username, password, fabric_nodes, **kwargs): result = FAIL_UF - headers = ['APIC', 'Firmware', 'md5sum', 'Failure'] + headers = ["APIC ID", "APIC Name", "Firmware", "md5sum", "Failure"] data = [] recommended_action = 'Delete the firmware from APIC and re-download' doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#apic-target-version-image-and-md5-hash" @@ -3286,7 +3293,7 @@ def apic_version_md5_check(tversion, username, password, fabric_nodes, **kwargs) desc = fm_mo["firmwareFirmware"]['attributes']["description"] md5 = fm_mo["firmwareFirmware"]['attributes']["checksum"] if "Image signing verification failed" in desc: - data.append(["All", str(tversion), md5, 'Target image is corrupted']) + data.append(["All", "-", str(tversion), md5, 'Target image is corrupted']) image_validaton = False if not image_validaton: @@ -3298,18 +3305,30 @@ def apic_version_md5_check(tversion, username, password, fabric_nodes, **kwargs) apics = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["role"] == "controller"] if not apics: return Result(result=ERROR, msg="No fabricNode of APIC. Is the cluster healthy?", doc_url=doc_url) + # `fabricNode` in pre-4.0 does not have `address` + if not apics[0]["fabricNode"]["attributes"].get("address"): + apic1 = [apic for apic in apics if apic["fabricNode"]["attributes"]["id"] == "1"][0] + apic1_dn = apic1["fabricNode"]["attributes"]["dn"] + apics = icurl("class", "{}/infraWiNode.json".format(apic1_dn)) has_error = False for apic in apics: - apic_name = apic['fabricNode']['attributes']['name'] + if apic.get("fabricNode"): + apic_id = apic["fabricNode"]["attributes"]["id"] + apic_name = apic["fabricNode"]["attributes"]["name"] + apic_addr = apic["fabricNode"]["attributes"]["address"] + else: + apic_id = apic["infraWiNode"]["attributes"]["id"] + apic_name = apic["infraWiNode"]["attributes"]["nodeName"] + apic_addr = apic["infraWiNode"]["attributes"]["addr"] try: - c = Connection(apic['fabricNode']['attributes']['address']) + c = Connection(apic_addr) c.username = username c.password = password c.log = LOG_FILE c.connect() except Exception as e: - data.append([apic_name, '-', '-', str(e)]) + data.append([apic_id, apic_name, '-', '-', str(e)]) has_error = True continue @@ -3317,24 +3336,24 @@ def apic_version_md5_check(tversion, username, password, fabric_nodes, **kwargs) c.cmd("ls -aslh /firmware/fwrepos/fwrepo/aci-apic-dk9.%s.bin" % tversion.dot_version) except Exception as e: - data.append([apic_name, '-', '-', + data.append([apic_id, apic_name, '-', '-', 'ls command via ssh failed due to:{}'.format(str(e))]) has_error = True continue if "No such file or directory" in c.output: - data.append([apic_name, str(tversion), '-', 'image not found']) + data.append([apic_id, apic_name, str(tversion), '-', 'image not found']) continue try: c.cmd("cat /firmware/fwrepos/fwrepo/md5sum/aci-apic-dk9.%s.bin" % tversion.dot_version) except Exception as e: - data.append([apic_name, str(tversion), '-', + data.append([apic_id, apic_name, str(tversion), '-', 'failed to check md5sum via ssh due to:{}'.format(str(e))]) has_error = True continue if "No such file or directory" in c.output: - data.append([apic_name, str(tversion), '-', 'md5sum file not found']) + data.append([apic_id, apic_name, str(tversion), '-', 'md5sum file not found']) continue for line in c.output.split("\n"): words = line.split() @@ -3343,16 +3362,16 @@ def apic_version_md5_check(tversion, username, password, fabric_nodes, **kwargs) words[1].startswith("/var/run/mgmt/fwrepos/fwrepo/aci-apic") ): md5s.append(words[0]) - md5_names.append(apic_name) + md5_names.append([apic_id, apic_name]) break else: - data.append([apic_name, str(tversion), '-', 'unexpected output when checking md5sum file']) + data.append([apic_id, apic_name, str(tversion), '-', 'unexpected output when checking md5sum file']) has_error = True continue if len(set(md5s)) > 1: - for name, md5 in zip(md5_names, md5s): - data.append([name, str(tversion), md5, 'md5sum do not match on all APICs']) + for id_name, md5 in zip(md5_names, md5s): + data.append([id_name[0], id_name[1], str(tversion), md5, 'md5sum do not match on all APICs']) if has_error: result = ERROR elif not data: @@ -5698,7 +5717,7 @@ def service_bd_forceful_routing_check(cversion, tversion, **kwargs): @check_wrapper(check_title='Observer Database Size') def observer_db_size_check(username, password, fabric_nodes, **kwargs): result = PASS - headers = ["Node", "File Location", "Size (GB)"] + headers = ["APIC ID", "APIC Name", "File Location", "Size (GB)"] data = [] recommended_action = 'Contact TAC to analyze and truncate large DB files' doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations#observer-database-size' @@ -5706,25 +5725,37 @@ def observer_db_size_check(username, password, fabric_nodes, **kwargs): apics = [node for node in fabric_nodes if node["fabricNode"]["attributes"]["role"] == "controller"] if not apics: return Result(result=ERROR, msg="No fabricNode of APIC. Is the cluster healthy?", doc_url=doc_url) + # `fabricNode` in pre-4.0 does not have `address` + if not apics[0]["fabricNode"]["attributes"].get("address"): + apic1 = [apic for apic in apics if apic["fabricNode"]["attributes"]["id"] == "1"][0] + apic1_dn = apic1["fabricNode"]["attributes"]["dn"] + apics = icurl("class", "{}/infraWiNode.json".format(apic1_dn)) has_error = False for apic in apics: - attr = apic['fabricNode']['attributes'] + if apic.get("fabricNode"): + apic_id = apic["fabricNode"]["attributes"]["id"] + apic_name = apic["fabricNode"]["attributes"]["name"] + apic_addr = apic["fabricNode"]["attributes"]["address"] + else: + apic_id = apic["infraWiNode"]["attributes"]["id"] + apic_name = apic["infraWiNode"]["attributes"]["nodeName"] + apic_addr = apic["infraWiNode"]["attributes"]["addr"] try: - c = Connection(attr['address']) + c = Connection(apic_addr) c.username = username c.password = password c.log = LOG_FILE c.connect() except Exception as e: - data.append([attr['id'], attr['name'], str(e)]) + data.append([apic_id, apic_name, "-", str(e)]) has_error = True continue try: cmd = r"ls -lh /data2/dbstats | awk '{print $5, $9}'" c.cmd(cmd) if "No such file or directory" in c.output: - data.append([attr['id'], '/data2/dbstats/ not found', "Check user permissions or retry as 'apic#fallback\\\\admin'"]) + data.append([apic_id, apic_name, '/data2/dbstats/ not found', "Check user permissions or retry as 'apic#fallback\\\\admin'"]) has_error = True continue dbstats = c.output.split("\n") @@ -5734,9 +5765,9 @@ def observer_db_size_check(username, password, fabric_nodes, **kwargs): if size_match: file_size = size_match.group("size") file_name = "/data2/dbstats/" + size_match.group("file") - data.append([attr['id'], file_name, file_size]) + data.append([apic_id, apic_name, file_name, file_size]) except Exception as e: - data.append([attr['id'], attr['name'], str(e)]) + data.append([apic_id, apic_name, "-", str(e)]) has_error = True continue if has_error: diff --git a/tests/checks/apic_ssd_check/fabricNode_old.json b/tests/checks/apic_ssd_check/fabricNode_old.json new file mode 100644 index 00000000..f71fb9fc --- /dev/null +++ b/tests/checks/apic_ssd_check/fabricNode_old.json @@ -0,0 +1,62 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "1", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic1", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-2", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "2", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic2", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-2/node-3", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "3", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic3", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "nodeType": "unspecified", + "id": "101", + "version": "", + "role": "leaf", + "adSt": "on", + "name": "leaf1", + "model": "N9K-C9396PX" + } + } + } +] diff --git a/tests/checks/apic_ssd_check/infraWiNode_apic1.json b/tests/checks/apic_ssd_check/infraWiNode_apic1.json new file mode 100644 index 00000000..b6626d02 --- /dev/null +++ b/tests/checks/apic_ssd_check/infraWiNode_apic1.json @@ -0,0 +1,62 @@ +[ + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.1", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-1", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "1", + "mbSn": "FCH1234ABCD", + "name": "", + "nodeName": "apic1", + "operSt": "available", + "podId": "0", + "targetMbSn": "" + } + } + }, + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.2", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-2", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "2", + "mbSn": "FCH1235ABCD", + "name": "", + "nodeName": "apic2", + "operSt": "available", + "podId": "0", + "targetMbSn": "" + } + } + }, + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.3", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-3", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "3", + "mbSn": "FCH1236ABCD", + "name": "", + "nodeName": "apic3", + "operSt": "available", + "podId": "1", + "targetMbSn": "" + } + } + } +] diff --git a/tests/checks/apic_ssd_check/test_apic_ssd_check.py b/tests/checks/apic_ssd_check/test_apic_ssd_check.py index c77ff57b..b0cc678f 100644 --- a/tests/checks/apic_ssd_check/test_apic_ssd_check.py +++ b/tests/checks/apic_ssd_check/test_apic_ssd_check.py @@ -13,6 +13,7 @@ faultInst = 'faultInst.json?query-target-filter=or(eq(faultInst.code,"F2731"),eq(faultInst.code,"F2732"))' +infraWiNode = "topology/pod-1/node-1/infraWiNode.json" apic_ips = [ node["fabricNode"]["attributes"]["address"] @@ -36,7 +37,7 @@ "4.2(7w)", read_data(dir, "fabricNode.json"), script.FAIL_UF, - [["2", "3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], + [["3", "apic3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], ), ( {faultInst: read_data(dir, "fault_F2731.json")}, @@ -45,7 +46,7 @@ "5.2(1h)", read_data(dir, "fabricNode.json"), script.FAIL_UF, - [["2", "3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], + [["3", "apic3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], ), # New Versions, F273x are effective and NOT raised ( @@ -74,7 +75,7 @@ "4.2(6o)", read_data(dir, "fabricNode.json"), script.FAIL_UF, - [["2", "3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], + [["3", "apic3", "/dev/sdb", "<5% (Fault F2731)", "Contact TAC for replacement"]], ), # --- Old Versions, no F273x was raised. --- @@ -98,9 +99,9 @@ read_data(dir, "fabricNode.json"), script.ERROR, [ - ["1", "1", "-", "-", "Simulated exception at connect()"], - ["1", "2", "-", "-", "Simulated exception at connect()"], - ["2", "3", "-", "-", "Simulated exception at connect()"], + ["1", "apic1", "-", "-", "Simulated exception at connect()"], + ["2", "apic2", "-", "-", "Simulated exception at connect()"], + ["3", "apic3", "-", "-", "Simulated exception at connect()"], ], ), # Exception failure at the grep command @@ -121,9 +122,9 @@ read_data(dir, "fabricNode.json"), script.ERROR, [ - ["1", "1", "-", "-", "Simulated exception at `grep` command"], - ["1", "2", "-", "-", "Simulated exception at `grep` command"], - ["2", "3", "-", "-", "Simulated exception at `grep` command"], + ["1", "apic1", "-", "-", "Simulated exception at `grep` command"], + ["2", "apic2", "-", "-", "Simulated exception at `grep` command"], + ["3", "apic3", "-", "-", "Simulated exception at `grep` command"], ], ), # SSD Wearout Indicator is less than 5 @@ -157,9 +158,9 @@ read_data(dir, "fabricNode.json"), script.FAIL_UF, [ - ["1", "1", "Solid State Disk", "4", "Contact TAC for replacement"], - ["1", "2", "Solid State Disk", "5", "No Action Required"], - ["2", "3", "Solid State Disk", "4", "Contact TAC for replacement"], + ["1", "apic1", "Solid State Disk", "4", "Contact TAC for replacement"], + ["2", "apic2", "Solid State Disk", "5", "No Action Required"], + ["3", "apic3", "Solid State Disk", "4", "Contact TAC for replacement"], ], ), # Pass @@ -181,6 +182,25 @@ script.PASS, [], ), + # Pass (pre-4.0 with infraWiNode) + ( + {faultInst: [], infraWiNode: read_data(dir, "infraWiNode_apic1.json")}, + False, + { + apic_ip: [ + { + "cmd": grep_cmd, + "output": "\n".join([grep_cmd, grep_output_no_hit]), + "exception": None, + }, + ] + for apic_ip in apic_ips + }, + "4.2(6o)", + read_data(dir, "fabricNode_old.json"), + script.PASS, + [], + ), ], ) def test_logic(run_check, mock_icurl, mock_conn, cversion, fabric_nodes, expected_result, expected_data): diff --git a/tests/checks/apic_version_md5_check/fabricNode_old.json b/tests/checks/apic_version_md5_check/fabricNode_old.json new file mode 100644 index 00000000..f71fb9fc --- /dev/null +++ b/tests/checks/apic_version_md5_check/fabricNode_old.json @@ -0,0 +1,62 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "1", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic1", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-2", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "2", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic2", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-2/node-3", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "3", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic3", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "nodeType": "unspecified", + "id": "101", + "version": "", + "role": "leaf", + "adSt": "on", + "name": "leaf1", + "model": "N9K-C9396PX" + } + } + } +] diff --git a/tests/checks/apic_version_md5_check/infraWiNode_apic1.json b/tests/checks/apic_version_md5_check/infraWiNode_apic1.json new file mode 100644 index 00000000..b6626d02 --- /dev/null +++ b/tests/checks/apic_version_md5_check/infraWiNode_apic1.json @@ -0,0 +1,62 @@ +[ + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.1", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-1", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "1", + "mbSn": "FCH1234ABCD", + "name": "", + "nodeName": "apic1", + "operSt": "available", + "podId": "0", + "targetMbSn": "" + } + } + }, + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.2", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-2", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "2", + "mbSn": "FCH1235ABCD", + "name": "", + "nodeName": "apic2", + "operSt": "available", + "podId": "0", + "targetMbSn": "" + } + } + }, + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.3", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-3", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "3", + "mbSn": "FCH1236ABCD", + "name": "", + "nodeName": "apic3", + "operSt": "available", + "podId": "1", + "targetMbSn": "" + } + } + } +] diff --git a/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py b/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py index f920ce0f..32605ad7 100644 --- a/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py +++ b/tests/checks/apic_version_md5_check/test_apic_version_md5_check.py @@ -13,6 +13,7 @@ api_firmware = "fwrepo/fw-aci-apic-dk9.6.0.5h.json" +api_infraWiNode = "topology/pod-1/node-1/infraWiNode.json" apic_ips = [ node["fabricNode"]["attributes"]["address"] @@ -62,7 +63,7 @@ "6.0(5h)", read_data(dir, "fabricNode.json"), script.FAIL_UF, - [["All", "6.0(5h)", "d5afca58fce2018495d068c000000000", "Target image is corrupted"]], + [["All", "-", "6.0(5h)", "d5afca58fce2018495d068c000000000", "Target image is corrupted"]], ), # No fabricNode for APICs ( @@ -83,9 +84,9 @@ read_data(dir, "fabricNode.json"), script.ERROR, [ - ["apic1", "-", "-", "Simulated exception at connect()"], - ["apic2", "-", "-", "Simulated exception at connect()"], - ["apic3", "-", "-", "Simulated exception at connect()"], + ["1", "apic1", "-", "-", "Simulated exception at connect()"], + ["2", "apic2", "-", "-", "Simulated exception at connect()"], + ["3", "apic3", "-", "-", "Simulated exception at connect()"], ], ), # Exception failure at the ls command @@ -106,9 +107,9 @@ read_data(dir, "fabricNode.json"), script.ERROR, [ - ["apic1", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], - ["apic2", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], - ["apic3", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], + ["1", "apic1", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], + ["2", "apic2", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], + ["3", "apic3", "-", "-", "ls command via ssh failed due to:Simulated exception at `ls` command"], ], ), # No such file output from the ls command @@ -129,9 +130,9 @@ read_data(dir, "fabricNode.json"), script.FAIL_UF, [ - ["apic1", "6.0(5h)", "-", "image not found"], - ["apic2", "6.0(5h)", "-", "image not found"], - ["apic3", "6.0(5h)", "-", "image not found"], + ["1", "apic1", "6.0(5h)", "-", "image not found"], + ["2", "apic2", "6.0(5h)", "-", "image not found"], + ["3", "apic3", "6.0(5h)", "-", "image not found"], ], ), # Exception failure at the cat command @@ -157,9 +158,9 @@ read_data(dir, "fabricNode.json"), script.ERROR, [ - ["apic1", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], - ["apic2", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], - ["apic3", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], + ["1", "apic1", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], + ["2", "apic2", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], + ["3", "apic3", "6.0(5h)", "-", "failed to check md5sum via ssh due to:Simulated exception at `cat` command"], ], ), # No such file output from the cat command @@ -185,9 +186,9 @@ read_data(dir, "fabricNode.json"), script.FAIL_UF, [ - ["apic1", "6.0(5h)", "-", "md5sum file not found"], - ["apic2", "6.0(5h)", "-", "md5sum file not found"], - ["apic3", "6.0(5h)", "-", "md5sum file not found"], + ["1", "apic1", "6.0(5h)", "-", "md5sum file not found"], + ["2", "apic2", "6.0(5h)", "-", "md5sum file not found"], + ["3", "apic3", "6.0(5h)", "-", "md5sum file not found"], ], ), # Unexpected output from the cat command @@ -213,9 +214,9 @@ read_data(dir, "fabricNode.json"), script.ERROR, [ - ["apic1", "6.0(5h)", "-", "unexpected output when checking md5sum file"], - ["apic2", "6.0(5h)", "-", "unexpected output when checking md5sum file"], - ["apic3", "6.0(5h)", "-", "unexpected output when checking md5sum file"], + ["1", "apic1", "6.0(5h)", "-", "unexpected output when checking md5sum file"], + ["2", "apic2", "6.0(5h)", "-", "unexpected output when checking md5sum file"], + ["3", "apic3", "6.0(5h)", "-", "unexpected output when checking md5sum file"], ], ), # Failure because md5sum on each APIC do not match @@ -264,9 +265,9 @@ read_data(dir, "fabricNode.json"), script.FAIL_UF, [ - ["apic1", "6.0(5h)", "d5afca58fce2018495d068c44eb4a547", "md5sum do not match on all APICs"], - ["apic2", "6.0(5h)", "d5afca58fce2018495d068c000000000", "md5sum do not match on all APICs"], - ["apic3", "6.0(5h)", "d5afca58fce2018495d068c44eb4a547", "md5sum do not match on all APICs"], + ["1", "apic1", "6.0(5h)", "d5afca58fce2018495d068c44eb4a547", "md5sum do not match on all APICs"], + ["2", "apic2", "6.0(5h)", "d5afca58fce2018495d068c000000000", "md5sum do not match on all APICs"], + ["3", "apic3", "6.0(5h)", "d5afca58fce2018495d068c44eb4a547", "md5sum do not match on all APICs"], ], ), # Pass @@ -293,6 +294,33 @@ script.PASS, [], ), + # Pass (pre-4.0 with infraWiNode) + ( + { + api_firmware: read_data(dir, "firmwareFirmware_6.0.5h.json"), + api_infraWiNode: read_data(dir, "infraWiNode_apic1.json"), + }, + False, + { + apic_ip: [ + { + "cmd": ls_cmd, + "output": "\n".join([ls_cmd, ls_output]), + "exception": None, + }, + { + "cmd": cat_cmd, + "output": "\n".join([cat_cmd, cat_output]), + "exception": None, + }, + ] + for apic_ip in apic_ips + }, + "6.0(5h)", + read_data(dir, "fabricNode_old.json"), + script.PASS, + [], + ), ], ) def test_logic(run_check, mock_icurl, mock_conn, tversion, fabric_nodes, expected_result, expected_data): diff --git a/tests/checks/observer_db_size_check/fabricNode_old.json b/tests/checks/observer_db_size_check/fabricNode_old.json new file mode 100644 index 00000000..f71fb9fc --- /dev/null +++ b/tests/checks/observer_db_size_check/fabricNode_old.json @@ -0,0 +1,62 @@ +[ + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-1", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "1", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic1", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-2", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "2", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic2", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-2/node-3", + "fabricSt": "unknown", + "nodeType": "unspecified", + "id": "3", + "version": "A", + "role": "controller", + "adSt": "on", + "name": "apic3", + "model": "APIC-SERVER-M1" + } + } + }, + { + "fabricNode": { + "attributes": { + "dn": "topology/pod-1/node-101", + "fabricSt": "active", + "nodeType": "unspecified", + "id": "101", + "version": "", + "role": "leaf", + "adSt": "on", + "name": "leaf1", + "model": "N9K-C9396PX" + } + } + } +] diff --git a/tests/checks/observer_db_size_check/infraWiNode_apic1.json b/tests/checks/observer_db_size_check/infraWiNode_apic1.json new file mode 100644 index 00000000..b6626d02 --- /dev/null +++ b/tests/checks/observer_db_size_check/infraWiNode_apic1.json @@ -0,0 +1,62 @@ +[ + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.1", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-1", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "1", + "mbSn": "FCH1234ABCD", + "name": "", + "nodeName": "apic1", + "operSt": "available", + "podId": "0", + "targetMbSn": "" + } + } + }, + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.2", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-2", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "2", + "mbSn": "FCH1235ABCD", + "name": "", + "nodeName": "apic2", + "operSt": "available", + "podId": "0", + "targetMbSn": "" + } + } + }, + { + "infraWiNode": { + "attributes": { + "addr": "10.0.0.3", + "adminSt": "in-service", + "apicMode": "active", + "cntrlSbstState": "approved", + "dn": "topology/pod-1/node-1/av/node-3", + "failoverStatus": "idle", + "health": "fully-fit", + "id": "3", + "mbSn": "FCH1236ABCD", + "name": "", + "nodeName": "apic3", + "operSt": "available", + "podId": "1", + "targetMbSn": "" + } + } + } +] diff --git a/tests/checks/observer_db_size_check/test_observer_db_size_check.py b/tests/checks/observer_db_size_check/test_observer_db_size_check.py index 022dba84..b70f791e 100644 --- a/tests/checks/observer_db_size_check/test_observer_db_size_check.py +++ b/tests/checks/observer_db_size_check/test_observer_db_size_check.py @@ -11,6 +11,8 @@ test_function = "observer_db_size_check" +infraWiNode = "topology/pod-1/node-1/infraWiNode.json" + fabricNodes = read_data(dir, "fabricNode.json") apic_ips = [ mo["fabricNode"]["attributes"]["address"] @@ -42,22 +44,24 @@ @pytest.mark.parametrize( - "fabric_nodes, conn_failure, conn_cmds, expected_result, expected_data", + "icurl_outputs, fabric_nodes, conn_failure, conn_cmds, expected_result, expected_data", [ # Connection failure ( + {}, fabricNodes, True, [], script.ERROR, [ - ["1", "apic1", "Simulated exception at connect()"], - ["2", "apic2", "Simulated exception at connect()"], - ["3", "apic3", "Simulated exception at connect()"], + ["1", "apic1", "-", "Simulated exception at connect()"], + ["2", "apic2", "-", "Simulated exception at connect()"], + ["3", "apic3", "-", "Simulated exception at connect()"], ], ), # Simulatated exception at `ls` command ( + {}, fabricNodes, False, { @@ -72,13 +76,14 @@ }, script.ERROR, [ - ["1", "apic1", "Simulated exception at `ls` command"], - ["2", "apic2", "Simulated exception at `ls` command"], - ["3", "apic3", "Simulated exception at `ls` command"], + ["1", "apic1", "-", "Simulated exception at `ls` command"], + ["2", "apic2", "-", "Simulated exception at `ls` command"], + ["3", "apic3", "-", "Simulated exception at `ls` command"], ], ), # dbstats dir not found/not accessible ( + {}, fabricNodes, False, { @@ -93,13 +98,14 @@ }, script.ERROR, [ - ["1", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], - ["2", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], - ["3", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], + ["1", "apic1", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], + ["2", "apic2", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], + ["3", "apic3", "/data2/dbstats/ not found", "Check user permissions or retry as 'apic#fallback\\\\admin'"], ], ), # dbstats dir found, all DBs under 1G ( + {}, fabricNodes, False, { @@ -115,8 +121,27 @@ script.PASS, [], ), + # dbstats dir found, all DBs under 1G (pre-4.0 with infraWiNode) + ( + {infraWiNode: read_data(dir, "infraWiNode_apic1.json")}, + read_data(dir, "fabricNode_old.json"), + False, + { + apic_ip: [ + { + "cmd": ls_cmd, + "output": "\n".join([ls_cmd, ls_output_neg]), + "exception": None, + } + ] + for apic_ip in apic_ips + }, + script.PASS, + [], + ), # dbstats dir found, found DBs over 1G ( + {}, fabricNodes, False, { @@ -131,16 +156,17 @@ }, script.FAIL_UF, [ - ["1", "/data2/dbstats/observer_8.db", "1.0G"], - ["1", "/data2/dbstats/observer_9.db", "12G"], - ["2", "/data2/dbstats/observer_8.db", "1.0G"], - ["2", "/data2/dbstats/observer_9.db", "12G"], - ["3", "/data2/dbstats/observer_8.db", "1.0G"], - ["3", "/data2/dbstats/observer_9.db", "12G"], + ["1", "apic1", "/data2/dbstats/observer_8.db", "1.0G"], + ["1", "apic1", "/data2/dbstats/observer_9.db", "12G"], + ["2", "apic2", "/data2/dbstats/observer_8.db", "1.0G"], + ["2", "apic2", "/data2/dbstats/observer_9.db", "12G"], + ["3", "apic3", "/data2/dbstats/observer_8.db", "1.0G"], + ["3", "apic3", "/data2/dbstats/observer_9.db", "12G"], ], ), # ERROR, fabricNode failure ( + {}, read_data(dir, "fabricNode_no_apic.json"), False, [], @@ -149,7 +175,7 @@ ), ], ) -def test_logic(run_check, fabric_nodes, mock_conn, expected_result, expected_data): +def test_logic(run_check, mock_icurl, fabric_nodes, mock_conn, expected_result, expected_data): result = run_check( username="fake_username", password="fake_password", From 62b5689b6de663ec782b01c2a56c6f3b504deb99 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Thu, 20 Nov 2025 23:58:50 -0800 Subject: [PATCH 09/16] test: fail mock_icurl when test data is not provided --- tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index aabe0120..348c7d70 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,8 +93,7 @@ def mock_icurl(monkeypatch, icurl_outputs): def _mock_icurl(apitype, query, page=0, page_size=100000): output = icurl_outputs.get(query) if output is None: - log.error("Query `%s` not found in test data", query) - data = {"totalCount": "0", "imdata": []} + raise KeyError("Query `{}` not found in test data".format(query)) elif isinstance(output, list): # icurl_outputs option 1 - output is imdata which is empty if not output: From ac81749b8564e892c138d508c71af918274375c1 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Fri, 21 Nov 2025 19:49:08 -0800 Subject: [PATCH 10/16] fix: Change error message for API timeout --- aci-preupgrade-validation-script.py | 6 +++++ tests/test_icurl.py | 36 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 043cfd87..4b62ed78 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1565,6 +1565,12 @@ def _icurl_error_handler(imdata): raise OldVerClassNotFound('Your current ACI version does not have requested class') elif "not found" in imdata[0]['error']['attributes']['text']: raise OldVerClassNotFound('Your current ACI version does not have requested class') + elif "Unable to deliver the message, Resolve timeout" in imdata[0]['error']['attributes']['text']: + msg = "API Query Timeout. APIC may be too busy. Try again later." + try: + raise TimeoutError(msg) # only from py3.3 + except NameError: + raise IOError(msg) else: raise Exception('API call failed! Check debug log') diff --git a/tests/test_icurl.py b/tests/test_icurl.py index 68417a18..957371c9 100644 --- a/tests/test_icurl.py +++ b/tests/test_icurl.py @@ -3,6 +3,14 @@ script = importlib.import_module("aci-preupgrade-validation-script") + +# We raise IOError instead of TimeoutError prior to py3.3 as it's not available +try: + TimeoutError = TimeoutError +except NameError: + TimeoutError = IOError + + # icurl queries fabricNodePEps = "fabricNodePEp.json" @@ -134,6 +142,34 @@ def test_icurl(mock_icurl, apitype, query, expected_result): ], script.OldVerClassNotFound, ), + # Query timeout (90 sec) - pre-4.1 + ( + [ + { + "error": { + "attributes": { + "code": "503", + "text": "Unable to deliver the message, Resolve timeout from (type/num/svc/shard) = apic:1:7:1, apic:1:7:32, apic:1:7:31, apic:1:7:30, apic:1:7:13, apic:1:7:12, apic:1:7:11, apic:1:7:10, apic:1:7:9, apic:1:7:8, apic:1:7:7, apic:1:7:3, apic:1:7:14, apic:1:7:15, apic:1:7:16, apic:1:7:17, apic:1:7:18, apic:1:7:19, apic:1:7:20, apic:1:7:21, apic:1:7:22, apic:1:7:23, apic:1:7:24, apic:1:7:25, apic:1:7:26, apic:1:7:27, apic:1:7:28, apic:1:7:29", + } + } + } + ], + TimeoutError, + ), + # Query timeout (90 sec) - from-4.1 + ( + [ + { + "error": { + "attributes": { + "code": "503", + "text": "Unable to deliver the message, Resolve timeout", + } + } + } + ], + TimeoutError, + ), ], ) def test_icurl_error_handler(imdata, expected_exception): From 4b4951e0d246096bb6e03d299a63bb6e86f84780 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Fri, 21 Nov 2025 22:41:51 -0800 Subject: [PATCH 11/16] fix: raise exception with empty imdata with totalCount>1 --- aci-preupgrade-validation-script.py | 25 ++++++++++++++++--------- tests/test_icurl.py | 6 +++--- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 4b62ed78..66ef7fe4 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -77,6 +77,14 @@ log = logging.getLogger() +# TimeoutError is only from py3.3 +try: + TimeoutError +except NameError: + class TimeoutError(Exception): + pass + + class OldVerClassNotFound(Exception): """ Later versions of ACI can have class properties not found in older versions """ pass @@ -1566,11 +1574,7 @@ def _icurl_error_handler(imdata): elif "not found" in imdata[0]['error']['attributes']['text']: raise OldVerClassNotFound('Your current ACI version does not have requested class') elif "Unable to deliver the message, Resolve timeout" in imdata[0]['error']['attributes']['text']: - msg = "API Query Timeout. APIC may be too busy. Try again later." - try: - raise TimeoutError(msg) # only from py3.3 - except NameError: - raise IOError(msg) + raise TimeoutError("API Timeout. APIC may be too busy. Try again later.") else: raise Exception('API call failed! Check debug log') @@ -1597,8 +1601,11 @@ def icurl(apitype, query, page_size=100000): page = 0 while total_cnt > len(total_imdata): data = _icurl(apitype, query, page, page_size) - if not data['imdata']: - break + # API queries may return empty even when totalCount is > 1 and the given page number + # should contain entries. This may happen when there are too many queries + # such as multiple same queries at the same time. + if int(data['totalCount']) > 1 and not data['imdata']: + raise Exception("API response empty with totalCount:{}. APIC may be too busy. Try again later.".format(data["totalCount"])) total_imdata += data['imdata'] total_cnt = int(data['totalCount']) page += 1 @@ -1642,7 +1649,7 @@ def get_credentials(): def get_target_version(arg_tversion): """ Returns: AciVersion instance """ if arg_tversion: - prints("Target APIC version is overridden to %s" % arg_tversion) + prints("Target APIC version is overridden to %s\n" % arg_tversion) try: target_version = AciVersion(arg_tversion) except ValueError as e: @@ -1695,7 +1702,7 @@ def get_fabric_nodes(): def get_current_versions(fabric_nodes, arg_cversion): """ Returns: AciVersion instances of APIC and lowest switch """ if arg_cversion: - prints("Current version is overridden to %s" % arg_cversion) + prints("Current version is overridden to %s\n" % arg_cversion) try: current_version = AciVersion(arg_cversion) except ValueError as e: diff --git a/tests/test_icurl.py b/tests/test_icurl.py index 957371c9..351e3cf5 100644 --- a/tests/test_icurl.py +++ b/tests/test_icurl.py @@ -4,11 +4,11 @@ script = importlib.import_module("aci-preupgrade-validation-script") -# We raise IOError instead of TimeoutError prior to py3.3 as it's not available +# TimeoutError is only from py3.3 try: - TimeoutError = TimeoutError + TimeoutError except NameError: - TimeoutError = IOError + TimeoutError = script.TimeoutError # icurl queries From 59abc443e6d0fabb5bbeb98fb2409e603693babb Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Fri, 21 Nov 2025 23:01:24 -0800 Subject: [PATCH 12/16] fix: pytest error for version display --- tests/test_common_data.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_common_data.py b/tests/test_common_data.py index 0f678ca7..b9276282 100644 --- a/tests/test_common_data.py +++ b/tests/test_common_data.py @@ -35,7 +35,7 @@ def mock_get_target_version(monkeypatch): def _mock_get_target_version(arg_tversion): if arg_tversion: - script.prints("Target APIC version is overridden to %s" % arg_tversion) + script.prints("Target APIC version is overridden to %s\n" % arg_tversion) try: target_version = AciVersion(arg_tversion) except ValueError as e: @@ -573,7 +573,9 @@ def test_tversion_invald(capsys, mock_icurl): Gathering Node Information... Current version is overridden to 6.0(1a) + Target APIC version is overridden to invalid_version + Parsing failure of ACI version `invalid_version` """ assert captured.out.endswith(expected_output), "captured.out is:\n{}".format(captured.out) @@ -589,6 +591,7 @@ def test_cversion_invald(capsys, mock_icurl): Gathering Node Information... Current version is overridden to invalid_version + Parsing failure of ACI version `invalid_version` """ assert captured.out.endswith(expected_output), "captured.out is:\n{}".format(captured.out) From 63bfe8dedffce6ab868125c7ac3481d7f626bae9 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Fri, 21 Nov 2025 23:22:00 -0800 Subject: [PATCH 13/16] fix: Abort correctly when user is not authorized to access firmwareCtrlrRunning --- aci-preupgrade-validation-script.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 66ef7fe4..23bad328 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1732,6 +1732,9 @@ def get_current_versions(fabric_nodes, arg_cversion): except ValueError: is_old_version = True apic1_firmware = icurl('mo', apic1_dn + "/sys/ctrlrfwstatuscont/ctrlrrunning.json") + if not apic1_firmware: + prints("Unable to find current APIC version.") + sys.exit(1) apic1_version = apic1_firmware[0]['firmwareCtrlrRunning']['attributes']['version'] apic_version = AciVersion(apic1_version) From 773507fdc1a81e8917a2f53defa7014ac5410ff6 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Mon, 24 Nov 2025 14:08:42 -0800 Subject: [PATCH 14/16] fix: Output from a timed out test thread task impacts other tests result --- tests/test_ThreadManager.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/test_ThreadManager.py b/tests/test_ThreadManager.py index 796e0c69..4b02f3bb 100644 --- a/tests/test_ThreadManager.py +++ b/tests/test_ThreadManager.py @@ -5,22 +5,29 @@ script = importlib.import_module("aci-preupgrade-validation-script") +global_timeout = False + + def task1(data=""): time.sleep(2.5) - print("Thread task1: Finishing with data {}".format(data)) + if not global_timeout: + print("Thread task1: Finishing with data {}".format(data)) def task2(data=""): time.sleep(0.5) - print("Thread task2: Finishing with data {}".format(data)) + if not global_timeout: + print("Thread task2: Finishing with data {}".format(data)) def task3(data=""): time.sleep(0.2) - print("Thread task3: Finishing with data {}".format(data)) + if not global_timeout: + print("Thread task3: Finishing with data {}".format(data)) def test_ThreadManager(capsys): + global global_timeout tm = script.ThreadManager( funcs=[task1, task2, task3], common_kwargs={"data": "common_data"}, @@ -30,6 +37,9 @@ def test_ThreadManager(capsys): tm.start() tm.join() + if tm.is_timeout(): + global_timeout = True + expected_output = """\ Thread task3: Finishing with data common_data Thread task2: Finishing with data common_data From deeffcac32c8f9a9d7ca51b26ded62a8f2adce3f Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Mon, 24 Nov 2025 15:35:35 -0800 Subject: [PATCH 15/16] fix: Raise exception with empty imdata with totalCount>0. Handle docker0 check error individually --- aci-preupgrade-validation-script.py | 11 ++++-- .../test_docker0_subent_overlap_check.py | 34 +++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index 23bad328..fd88d3e9 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -1601,10 +1601,10 @@ def icurl(apitype, query, page_size=100000): page = 0 while total_cnt > len(total_imdata): data = _icurl(apitype, query, page, page_size) - # API queries may return empty even when totalCount is > 1 and the given page number + # API queries may return empty even when totalCount is > 0 and the given page number # should contain entries. This may happen when there are too many queries # such as multiple same queries at the same time. - if int(data['totalCount']) > 1 and not data['imdata']: + if int(data['totalCount']) > 0 and not data['imdata']: raise Exception("API response empty with totalCount:{}. APIC may be too busy. Try again later.".format(data["totalCount"])) total_imdata += data['imdata'] total_cnt = int(data['totalCount']) @@ -3749,13 +3749,18 @@ def bgp_golf_route_target_type_check(cversion, tversion, **kwargs): @check_wrapper(check_title="APIC Container Bridge IP Overlap with APIC TEP") -def docker0_subnet_overlap_check(**kwargs): +def docker0_subnet_overlap_check(cversion, **kwargs): result = PASS headers = ["Container Bridge IP", "APIC TEP"] data = [] recommended_action = 'Change the container bridge IP via "Apps > Settings" on the APIC GUI' doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#apic-container-bridge-ip-overlap-with-apic-tep" + # AppCenter was deprecated in 6.1.2. + # Due to a bug the deprecated object returns totalCount:1 with empty data instead of totalCount:0. + if cversion.newer_than("6.1(2a)"): + return Result(result=NA, msg=VER_NOT_AFFECTED) + containerPols = icurl('mo', 'pluginPolContr/ContainerPol.json') if not containerPols: bip = "172.17.0.1/16" diff --git a/tests/checks/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py b/tests/checks/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py index a8d9da01..be655c62 100644 --- a/tests/checks/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py +++ b/tests/checks/docker0_subent_overlap_check/test_docker0_subent_overlap_check.py @@ -17,13 +17,22 @@ @pytest.mark.parametrize( - "icurl_outputs, expected_result", + "icurl_outputs, cversion, expected_result", [ ( { infraWiNode: read_data(dir, "infraWiNode_10_0_0_0__16.json"), apContainerPol: [], }, + "6.1(2b)", + script.NA, + ), + ( + { + infraWiNode: read_data(dir, "infraWiNode_10_0_0_0__16.json"), + apContainerPol: [], + }, + "5.3(2b)", script.PASS, ), ( @@ -31,6 +40,7 @@ infraWiNode: read_data(dir, "infraWiNode_10_0_0_0__16.json"), apContainerPol: read_data(dir, "apContainerPol_172_17_0_1__16.json"), }, + "5.3(2b)", script.PASS, ), ( @@ -38,6 +48,7 @@ infraWiNode: read_data(dir, "infraWiNode_10_0_0_0__16.json"), apContainerPol: read_data(dir, "apContainerPol_10_0_0_1__16.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -45,6 +56,7 @@ infraWiNode: read_data(dir, "infraWiNode_10_0_x_0__24_remote_apic.json"), apContainerPol: [], }, + "5.3(2b)", script.PASS, ), ( @@ -52,6 +64,7 @@ infraWiNode: read_data(dir, "infraWiNode_10_0_x_0__24_remote_apic.json"), apContainerPol: read_data(dir, "apContainerPol_172_17_0_1__16.json"), }, + "5.3(2b)", script.PASS, ), ( @@ -59,6 +72,7 @@ infraWiNode: read_data(dir, "infraWiNode_10_0_x_0__24_remote_apic.json"), apContainerPol: read_data(dir, "apContainerPol_10_0_0_1__16.json"), }, + "5.3(2b)", script.FAIL_UF, ), # This scenario is the most likely one where, prior to the upgrade, @@ -70,6 +84,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_0_0__16.json"), apContainerPol: [], }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -77,6 +92,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_0_0__16.json"), apContainerPol: read_data(dir, "apContainerPol_172_17_0_1__16.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -84,6 +100,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_0_0__16.json"), apContainerPol: read_data(dir, "apContainerPol_172_17_0_10__16.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -91,6 +108,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_0_0__16.json"), apContainerPol: read_data(dir, "apContainerPol_172_17_0_1__17.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -98,6 +116,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_0_0__16.json"), apContainerPol: read_data(dir, "apContainerPol_172_16_0_1__15.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -105,6 +124,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_0_0__16.json"), apContainerPol: read_data(dir, "apContainerPol_172_18_0_1__16.json"), }, + "5.3(2b)", script.PASS, ), ( @@ -112,6 +132,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_x_0__24_remote_apic.json"), apContainerPol: [], }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -119,6 +140,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_x_0__24_remote_apic.json"), apContainerPol: read_data(dir, "apContainerPol_172_17_0_1__16.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -126,6 +148,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_x_0__24_remote_apic.json"), apContainerPol: read_data(dir, "apContainerPol_172_17_0_10__16.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -133,6 +156,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_x_0__24_remote_apic.json"), apContainerPol: read_data(dir, "apContainerPol_172_17_0_1__17.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -140,6 +164,7 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_x_0__24_remote_apic.json"), apContainerPol: read_data(dir, "apContainerPol_172_16_0_1__15.json"), }, + "5.3(2b)", script.FAIL_UF, ), ( @@ -147,10 +172,13 @@ infraWiNode: read_data(dir, "infraWiNode_172_17_x_0__24_remote_apic.json"), apContainerPol: read_data(dir, "apContainerPol_172_18_0_1__16.json"), }, + "5.3(2b)", script.PASS, ), ], ) -def test_logic(run_check, mock_icurl, expected_result): - result = run_check() +def test_logic(run_check, mock_icurl, cversion, expected_result): + result = run_check( + cversion=script.AciVersion(cversion), + ) assert result.result == expected_result From 7f638ea205d7883549d144365b1e4b8fe84b01a0 Mon Sep 17 00:00:00 2001 From: tkishida <tkishida@cisco.com> Date: Tue, 25 Nov 2025 14:20:30 -0800 Subject: [PATCH 16/16] Bump to v4.0.0 --- aci-preupgrade-validation-script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index fd88d3e9..bfca5bb6 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -38,7 +38,7 @@ import os import re -SCRIPT_VERSION = "v3.4.14" +SCRIPT_VERSION = "v4.0.0" DEFAULT_TIMEOUT = 600 # sec # result constants DONE = 'DONE'