@@ -1532,7 +1532,7 @@ def _create_deployment_parameters(
15321532 )
15331533
15341534 # composite deployment properties
1535- parameters = arm_parameters .to_dict () # type:ignore
1535+ parameters = arm_parameters .to_dict () # type: ignore
15361536 parameters = {k : {"value" : v } for k , v in parameters .items ()}
15371537 log .debug (f"parameters: { parameters } " )
15381538 deployment_properties = DeploymentProperties (
@@ -3109,6 +3109,38 @@ def _get_meet_capabilities(
31093109
31103110 return False
31113111
3112+ @staticmethod
3113+ def _collect_unmet_reasons (
3114+ awaitable_candidates : List [Any ],
3115+ all_candidates : List [Any ],
3116+ ) -> List [str ]:
3117+ """Collect capability mismatch reasons from candidates.
3118+
3119+ First checks reasons stored by _get_meet_capabilities during its
3120+ iteration. If none were found (e.g. all candidates were dropped due
3121+ to zero quota before capability matching), falls back to running
3122+ capability checks against ALL allowed (pre-quota-filtered) candidates
3123+ to surface the real mismatch reasons.
3124+ """
3125+ unmet_reasons : List [str ] = []
3126+ for item in awaitable_candidates :
3127+ if len (item ) > 2 and item [2 ]:
3128+ unmet_reasons .extend (item [2 ])
3129+
3130+ if not unmet_reasons and all_candidates :
3131+ for item in all_candidates :
3132+ req_check , caps_check = item [0 ], item [1 ]
3133+ assert isinstance (req_check , schema .NodeSpace )
3134+ for azure_cap in caps_check :
3135+ check_result = req_check .check (azure_cap .capability )
3136+ if not check_result .result and check_result .reasons :
3137+ unmet_reasons .extend (check_result .reasons )
3138+ break
3139+ if unmet_reasons :
3140+ break
3141+
3142+ return unmet_reasons
3143+
31123144 def _get_azure_capabilities (
31133145 self , location : str , nodes_requirement : List [schema .NodeSpace ], log : Logger
31143146 ) -> Tuple [List [Union [AzureCapability , bool ]], str ]:
@@ -3119,6 +3151,10 @@ def _get_azure_capabilities(
31193151 # capabilities.
31203152 available_candidates : List [Any ] = []
31213153 awaitable_candidates : List [Any ] = []
3154+ # Keep all allowed candidates (before quota filtering) so we can
3155+ # run capability checks on them to collect meaningful skip reasons
3156+ # even for VMs that were dropped due to zero quota.
3157+ all_candidates : List [Any ] = []
31223158
31233159 # get allowed vm sizes. Either it's from the runbook defined, or
31243160 # from subscription supported.
@@ -3131,6 +3167,8 @@ def _get_azure_capabilities(
31313167 error = sub_error
31323168 continue
31333169
3170+ all_candidates .append ([req , candidate_caps ])
3171+
31343172 # filter vm sizes and return two list. 1st is deployable, 2nd is
31353173 # wait able for released resource.
31363174 (
@@ -3191,14 +3229,12 @@ def _get_azure_capabilities(
31913229
31923230 if not found :
31933231 # Collect unmet requirement reasons that were stored by
3194- # _get_meet_capabilities during its iteration. These explain
3195- # exactly which test-case requirements were not satisfied by
3196- # any candidate VM size, replacing the previously generic
3197- # "no available quota found" message.
3198- unmet_reasons : List [str ] = []
3199- for item in awaitable_candidates :
3200- if len (item ) > 2 and item [2 ]:
3201- unmet_reasons .extend (item [2 ])
3232+ # _get_meet_capabilities during its iteration, or fall back
3233+ # to checking all pre-quota-filtered candidates.
3234+ unmet_reasons = self ._collect_unmet_reasons (
3235+ awaitable_candidates , all_candidates
3236+ )
3237+
32023238 if unmet_reasons :
32033239 # De-duplicate while preserving order.
32043240 seen : Set [str ] = set ()
0 commit comments