Skip to content

Commit 900ba0e

Browse files
Merge pull request #128 from microsoft/feature/user-interactive-quota-check
feat: implement interactive quota check script with user inputs
2 parents 2eba2ec + a68b091 commit 900ba0e

5 files changed

Lines changed: 167 additions & 118 deletions

File tree

azure.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ hooks:
2727
shell: sh
2828
run: >
2929
chmod u+r+x ./scripts/validate_model_deployment_quota.sh; chmod u+r+x ./scripts/validate_model_quota.sh; ./scripts/validate_model_deployment_quota.sh --SubscriptionId "$AZURE_SUBSCRIPTION_ID" --Location "${AZURE_AISERVICE_LOCATION:-japaneast}" --ModelsParameter "aiModelDeployments"
30-
interactive: false
30+
interactive: true
3131
continueOnError: false
3232

3333
windows:
3434
shell: pwsh
3535
run: >
3636
$location = if ($env:AZURE_AISERVICE_LOCATION) { $env:AZURE_AISERVICE_LOCATION } else { "japaneast" };
3737
./scripts/validate_model_deployment_quota.ps1 -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -Location $location -ModelsParameter "aiModelDeployments"
38-
interactive: false
38+
interactive: true
3939
continueOnError: false

scripts/validate_model_deployment_quota.ps1

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,58 +15,64 @@ if (-not $ModelsParameter) { $MissingParams += "ModelsParameter" }
1515

1616
if ($MissingParams.Count -gt 0) {
1717
Write-Error "❌ ERROR: Missing required parameters: $($MissingParams -join ', ')"
18+
Write-Host "Usage: validate_model_deployment_quota.ps1 -SubscriptionId <SUBSCRIPTION_ID> -Location <LOCATION> -ModelsParameter <MODELS_PARAMETER>"
1819
exit 1
1920
}
2021

2122
# Load model deployments from parameter file
2223
$JsonContent = Get-Content -Path "./infra/main.parameters.json" -Raw | ConvertFrom-Json
23-
if (-not $JsonContent) {
24-
Write-Error "❌ ERROR: Failed to parse main.parameters.json. Ensure the JSON file is valid."
25-
exit 1
26-
}
27-
2824
$aiModelDeployments = $JsonContent.parameters.$ModelsParameter.value
2925
if (-not $aiModelDeployments -or -not ($aiModelDeployments -is [System.Collections.IEnumerable])) {
30-
Write-Error "❌ ERROR: The specified property '$ModelsParameter' does not exist or is not an array."
26+
Write-Error "❌ ERROR: Failed to parse main.parameters.json or missing '$ModelsParameter'"
3127
exit 1
3228
}
3329

34-
# Check if AI Foundry exists and has all required model deployments
35-
$existing = $null
30+
# Try to discover AI Foundry name if not set
31+
if (-not $AiFoundryName -and $ResourceGroup) {
32+
$AiFoundryName = az cognitiveservices account list `
33+
--resource-group $ResourceGroup `
34+
--query "sort_by([?kind=='AIServices'], &name)[0].name" `
35+
-o tsv 2>$null
36+
}
37+
38+
# Check if AI Foundry exists
3639
if ($AiFoundryName -and $ResourceGroup) {
3740
$existing = az cognitiveservices account show `
3841
--name $AiFoundryName `
3942
--resource-group $ResourceGroup `
4043
--query "name" --output tsv 2>$null
41-
}
4244

43-
if ($existing) {
44-
$deployedModelsOutput = az cognitiveservices account deployment list `
45-
--name $AiFoundryName `
46-
--resource-group $ResourceGroup `
47-
--query "[].name" --output tsv 2>$null
45+
if ($existing) {
46+
# adding into .env
47+
azd env set AZURE_AIFOUNDRY_NAME $existing | Out-Null
4848

49-
$deployedModels = @()
50-
if ($deployedModelsOutput -is [string]) {
51-
$deployedModels += $deployedModelsOutput
52-
} elseif ($deployedModelsOutput) {
53-
$deployedModels = $deployedModelsOutput -split "`r?`n"
54-
}
49+
$deployedModelsOutput = az cognitiveservices account deployment list `
50+
--name $AiFoundryName `
51+
--resource-group $ResourceGroup `
52+
--query "[].name" --output tsv 2>$null
5553

56-
$requiredDeployments = $aiModelDeployments | ForEach-Object { $_.name }
57-
$missingDeployments = $requiredDeployments | Where-Object { $_ -notin $deployedModels }
54+
$deployedModels = @()
55+
if ($deployedModelsOutput -is [string]) {
56+
$deployedModels += $deployedModelsOutput
57+
} elseif ($deployedModelsOutput) {
58+
$deployedModels = $deployedModelsOutput -split "`r?`n"
59+
}
60+
61+
$requiredDeployments = $aiModelDeployments | ForEach-Object { $_.name }
62+
$missingDeployments = $requiredDeployments | Where-Object { $_ -notin $deployedModels }
5863

59-
if ($missingDeployments.Count -eq 0) {
60-
Write-Host "ℹ️ AI Foundry '$AiFoundryName' exists and all required model deployments are already provisioned."
61-
Write-Host "⏭️ Skipping quota validation."
62-
exit 0
63-
} else {
64-
Write-Host "🔍 AI Foundry exists, but the following model deployments are missing: $($missingDeployments -join ', ')"
65-
Write-Host "➡️ Proceeding with quota validation for missing models..."
64+
if ($missingDeployments.Count -eq 0) {
65+
Write-Host "ℹ️ AI Foundry '$AiFoundryName' exists and all required model deployments are already provisioned."
66+
Write-Host "⏭️ Skipping quota validation."
67+
exit 0
68+
} else {
69+
Write-Host "🔍 AI Foundry exists, but the following model deployments are missing: $($missingDeployments -join ', ')"
70+
Write-Host "➡️ Proceeding with quota validation for missing models..."
71+
}
6672
}
6773
}
6874

69-
# Run quota validation for all models
75+
# Run quota validation
7076
az account set --subscription $SubscriptionId
7177
Write-Host "🎯 Active Subscription: $(az account show --query '[name, id]' --output tsv)"
7278

@@ -78,7 +84,8 @@ foreach ($deployment in $aiModelDeployments) {
7884
$type = if ($env:AZURE_ENV_MODEL_DEPLOYMENT_TYPE) { $env:AZURE_ENV_MODEL_DEPLOYMENT_TYPE } else { $deployment.sku.name }
7985
$capacity = if ($env:AZURE_ENV_MODEL_CAPACITY) { $env:AZURE_ENV_MODEL_CAPACITY } else { $deployment.sku.capacity }
8086

81-
Write-Host "`n🔍 Validating model deployment: $name ..."
87+
Write-Host ""
88+
Write-Host "🔍 Validating model deployment: $name ..."
8289
& .\scripts\validate_model_quota.ps1 -Location $Location -Model $model -Capacity $capacity -DeploymentType $type
8390
$exitCode = $LASTEXITCODE
8491

scripts/validate_model_deployment_quota.sh

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,34 +47,44 @@ if [[ $? -ne 0 || -z "$aiModelDeployments" ]]; then
4747
exit 1
4848
fi
4949

50-
# Check if AI Foundry exists and has all required model deployments
51-
existing=""
52-
if [[ -n "$AIFOUNDRY_NAME" && -n "$RESOURCE_GROUP" ]]; then
53-
existing=$(az cognitiveservices account show --name "$AIFOUNDRY_NAME" --resource-group "$RESOURCE_GROUP" --query "name" --output tsv 2>/dev/null)
50+
# Try to discover AI Foundry name if not set
51+
if [[ -z "$AIFOUNDRY_NAME" && -n "$RESOURCE_GROUP" ]]; then
52+
AIFOUNDRY_NAME=$(az cognitiveservices account list --resource-group "$RESOURCE_GROUP" \
53+
--query "sort_by([?kind=='AIServices'], &name)[0].name" -o tsv 2>/dev/null)
5454
fi
5555

56-
if [[ -n "$existing" ]]; then
57-
existing_deployments=$(az cognitiveservices account deployment list \
58-
--name "$AIFOUNDRY_NAME" \
59-
--resource-group "$RESOURCE_GROUP" \
60-
--query "[].name" --output tsv 2>/dev/null)
61-
62-
required_models=$(jq -r ".parameters.$MODELS_PARAMETER.value[].name" ./infra/main.parameters.json)
63-
64-
missing_models=()
65-
for model in $required_models; do
66-
if ! grep -q -w "$model" <<< "$existing_deployments"; then
67-
missing_models+=("$model")
56+
# Check if AI Foundry exists
57+
if [[ -n "$AIFOUNDRY_NAME" && -n "$RESOURCE_GROUP" ]]; then
58+
existing=$(az cognitiveservices account show --name "$AIFOUNDRY_NAME" \
59+
--resource-group "$RESOURCE_GROUP" --query "name" --output tsv 2>/dev/null)
60+
61+
if [[ -n "$existing" ]]; then
62+
# adding into .env
63+
azd env set AZURE_AIFOUNDRY_NAME "$existing" > /dev/null
64+
65+
# Check model deployments
66+
existing_deployments=$(az cognitiveservices account deployment list \
67+
--name "$AIFOUNDRY_NAME" \
68+
--resource-group "$RESOURCE_GROUP" \
69+
--query "[].name" --output tsv 2>/dev/null)
70+
71+
required_models=$(jq -r ".parameters.$MODELS_PARAMETER.value[].name" ./infra/main.parameters.json)
72+
73+
missing_models=()
74+
for model in $required_models; do
75+
if ! grep -q -w "$model" <<< "$existing_deployments"; then
76+
missing_models+=("$model")
77+
fi
78+
done
79+
80+
if [[ ${#missing_models[@]} -eq 0 ]]; then
81+
echo "ℹ️ AI Foundry '$AIFOUNDRY_NAME' exists and all required model deployments are already provisioned."
82+
echo "⏭️ Skipping quota validation."
83+
exit 0
84+
else
85+
echo "🔍 AI Foundry exists, but the following model deployments are missing: ${missing_models[*]}"
86+
echo "➡️ Proceeding with quota validation for missing models..."
6887
fi
69-
done
70-
71-
if [[ ${#missing_models[@]} -eq 0 ]]; then
72-
echo "ℹ️ AI Foundry '$AIFOUNDRY_NAME' exists and all required model deployments are already provisioned."
73-
echo "⏭️ Skipping quota validation."
74-
exit 0
75-
else
76-
echo "🔍 AI Foundry exists, but the following model deployments are missing: ${missing_models[*]}"
77-
echo "➡️ Proceeding with quota validation for missing models..."
7888
fi
7989
fi
8090

scripts/validate_model_quota.ps1

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ $MissingParams = @()
1010
if (-not $Location) { $MissingParams += "location" }
1111
if (-not $Model) { $MissingParams += "model" }
1212
if (-not $Capacity) { $MissingParams += "capacity" }
13-
if (-not $DeploymentType) { $MissingParams += "deployment-type" }
1413

1514
if ($MissingParams.Count -gt 0) {
1615
Write-Error "❌ ERROR: Missing required parameters: $($MissingParams -join ', ')"
@@ -35,7 +34,7 @@ function Check-Quota {
3534
try {
3635
$ModelInfoRaw = az cognitiveservices usage list --location $Region --query "[?name.value=='$ModelType']" --output json
3736
$ModelInfo = $ModelInfoRaw | ConvertFrom-Json
38-
if (-not $ModelInfo) { return }
37+
if (-not $ModelInfo) { return $null }
3938

4039
$CurrentValue = ($ModelInfo | Where-Object { $_.name.value -eq $ModelType }).currentValue
4140
$Limit = ($ModelInfo | Where-Object { $_.name.value -eq $ModelType }).limit
@@ -52,11 +51,23 @@ function Check-Quota {
5251
Available = $Available
5352
}
5453
} catch {
55-
return
54+
return $null
5655
}
5756
}
5857

59-
# First, check the user-specified region
58+
function Show-Table {
59+
Write-Host "`n--------------------------------------------------------------------------------------------"
60+
Write-Host "| No. | Region | Model Name | Limit | Used | Available |"
61+
Write-Host "--------------------------------------------------------------------------------------------"
62+
$count = 1
63+
foreach ($entry in $AllResults) {
64+
Write-Host ("| {0,-3} | {1,-14} | {2,-35} | {3,-5} | {4,-5} | {5,-9} |" -f $count, $entry.Region, $entry.Model, $entry.Limit, $entry.Used, $entry.Available)
65+
$count++
66+
}
67+
Write-Host "--------------------------------------------------------------------------------------------"
68+
}
69+
70+
# ----------- First check the user-specified region -----------
6071
Write-Host "`n🔍 Checking quota in the requested region '$Location'..."
6172
$PrimaryResult = Check-Quota -Region $Location
6273

@@ -72,45 +83,53 @@ if ($PrimaryResult) {
7283
Write-Host "`n⚠️ Could not retrieve quota info for region '$Location'. Checking fallback regions..."
7384
}
7485

75-
# Remove primary region from fallback list
86+
# ----------- Check all other fallback regions -----------
7687
$FallbackRegions = $PreferredRegions | Where-Object { $_ -ne $Location }
88+
$EligibleFallbacks = @()
7789

7890
foreach ($region in $FallbackRegions) {
7991
$result = Check-Quota -Region $region
8092
if ($result) {
8193
$AllResults += $result
94+
if ($result.Available -ge $Capacity) {
95+
$EligibleFallbacks += $result
96+
}
8297
}
8398
}
8499

85-
# Display Results Table
86-
Write-Host "`n-------------------------------------------------------------------------------------------------------------"
87-
Write-Host "| No. | Region | Model Name | Limit | Used | Available |"
88-
Write-Host "-------------------------------------------------------------------------------------------------------------"
89-
90-
$count = 1
91-
foreach ($entry in $AllResults) {
92-
$modelShort = $entry.Model.Substring($entry.Model.LastIndexOf(".") + 1)
93-
Write-Host ("| {0,-4} | {1,-16} | {2,-35} | {3,-7} | {4,-7} | {5,-9} |" -f $count, $entry.Region, $entry.Model, $entry.Limit, $entry.Used, $entry.Available)
94-
$count++
95-
}
96-
Write-Host "-------------------------------------------------------------------------------------------------------------"
97-
98-
# Suggest fallback regions
99-
$EligibleFallbacks = $AllResults | Where-Object { $_.Region -ne $Location -and $_.Available -ge $Capacity }
100+
# ----------- Show Table of All Regions Checked -----------
101+
$AllResults = $AllResults | Where-Object { $_.Available -gt 50 }
102+
Show-Table
100103

104+
# ----------- If eligible fallback regions found, ask user -----------
101105
if ($EligibleFallbacks.Count -gt 0) {
102106
Write-Host "`n❌ Deployment cannot proceed in '$Location'."
103-
Write-Host "➡️ You can retry using one of the following regions with sufficient quota:`n"
104-
foreach ($region in $EligibleFallbacks) {
105-
Write-Host "$($region.Region) (Available: $($region.Available))"
106-
}
107+
Write-Host "➡️ Found fallback regions with sufficient quota."
108+
109+
while ($true) {
110+
Write-Host "`nPlease enter a fallback region from the list above to proceed:"
111+
$NewLocation = Read-Host "Enter region"
112+
113+
if (-not $NewLocation) {
114+
Write-Host "❌ No location entered. Exiting."
115+
exit 1
116+
}
117+
118+
$UserResult = Check-Quota -Region $NewLocation
119+
if (-not $UserResult) {
120+
Write-Host "⚠️ Could not retrieve quota info for region '$NewLocation'. Try again."
121+
continue
122+
}
107123

108-
Write-Host "`n🔧 To proceed, run:"
109-
Write-Host " azd env set AZURE_AISERVICE_LOCATION '<region>'"
110-
Write-Host "📌 To confirm it's set correctly, run:"
111-
Write-Host " azd env get-value AZURE_AISERVICE_LOCATION"
112-
Write-Host "▶️ Once confirmed, re-run azd up to deploy the model in the new region."
113-
exit 2
124+
if ($UserResult.Available -ge $Capacity) {
125+
Write-Host "✅ Sufficient quota found in '$NewLocation'. Proceeding with deployment."
126+
azd env set AZURE_AISERVICE_LOCATION "$NewLocation" | Out-Null
127+
Write-Host "➡️ Set AZURE_AISERVICE_LOCATION to '$NewLocation'."
128+
exit 0
129+
} else {
130+
Write-Host "❌ Insufficient quota in '$NewLocation'. Try another."
131+
}
132+
}
114133
}
115134

116135
Write-Error "`n❌ ERROR: No available quota found in any region."

0 commit comments

Comments
 (0)