diff --git a/docs/quota_check.md b/docs/quota_check.md index d809a32..a505fb1 100644 --- a/docs/quota_check.md +++ b/docs/quota_check.md @@ -1,7 +1,7 @@ # Check Quota Availability Before Deployment -Before deploying the accelerator, **ensure sufficient quota availability** for the required model. -> **We recommend increasing the capacity to 100k tokens for optimal performance.** +Before deploying the accelerator, **ensure sufficient quota availability** for the required AI models and Fabric capacity. +> **The default capacities match the deployment parameters in `infra/main.bicepparam`.** ## Login if you have not done so already ``` @@ -9,56 +9,89 @@ az login ``` ## 📌 Default Models & Capacities: +These match the `modelDeploymentList` in the Bicep parameters: ``` -gpt-4o:150, gpt-4o-mini:150, gpt-4:150, text-embedding-3-small:100 +gpt-4.1-mini:40:GlobalStandard, text-embedding-3-large:40:Standard ``` + ## 📌 Default Regions: ``` -eastus, uksouth, eastus2, northcentralus, swedencentral, westus, westus2, southcentralus, canadacentral, australiaeast, japaneast, norwayeast +eastus, eastus2, swedencentral, uksouth, westus, westus2, southcentralus, canadacentral, australiaeast, japaneast, norwayeast ``` + +## 📌 Optional: Fabric Capacity Check +The accelerator also deploys a **Microsoft Fabric F8** capacity. Pass `--check-fabric` (bash) or `-CheckFabric` (PowerShell) to verify Fabric SKU availability. + ## Usage Scenarios: - No parameters passed → Default models and capacities will be checked in default regions. - Only model(s) provided → The script will check for those models in the default regions. - Only region(s) provided → The script will check default models in the specified regions. - Both models and regions provided → The script will check those models in the specified regions. - `--verbose` passed → Enables detailed logging output for debugging and traceability. +- `--check-fabric` passed → Also checks Microsoft Fabric capacity availability. -## **Input Formats** -> Use the --models, --regions, and --verbose options for parameter handling: +## **Input Formats — Bash** +> Use the --models, --regions, --verbose, and --check-fabric options for parameter handling: -✔️ Run without parameters to check default models & regions without verbose logging: - ``` - ./quota_check.sh +✔️ Run without parameters to check default models & regions: + ```sh + ./quota_check.sh ``` ✔️ Enable verbose logging: - ``` - ./quota_check.sh --verbose + ```sh + ./quota_check.sh --verbose ``` ✔️ Check specific model(s) in default regions: - ``` - ./quota_check.sh --models gpt-4o:150,text-embedding-3-small:100 + ```sh + ./quota_check.sh --models gpt-4.1-mini:40:GlobalStandard,text-embedding-3-large:40:Standard ``` ✔️ Check default models in specific region(s): - ``` -./quota_check.sh --regions eastus,westus - ``` -✔️ Passing Both models and regions: - ``` - ./quota_check.sh --models gpt-4o:150 --regions eastus,westus2 + ```sh + ./quota_check.sh --regions eastus,westus ``` ✔️ All parameters combined: + ```sh + ./quota_check.sh --models gpt-4.1-mini:40 --regions eastus,westus --verbose + ``` +✔️ Also check Fabric capacity availability: + ```sh + ./quota_check.sh --check-fabric --verbose ``` - ./quota_check.sh --models gpt-4:150,text-embedding-3-small:100 --regions eastus,westus --verbose + +## **Input Formats — PowerShell** +> Use the -Models, -Regions, -Verbose, and -CheckFabric parameters: + +✔️ Run without parameters: + ```powershell + .\quota_check.ps1 + ``` +✔️ Check specific model(s): + ```powershell + .\quota_check.ps1 -Models "gpt-4.1-mini:40:GlobalStandard,text-embedding-3-large:40:Standard" ``` -✔️ Multiple models with single region: +✔️ Check specific region(s): + ```powershell + .\quota_check.ps1 -Regions "eastus,westus2" ``` - ./quota_check.sh --models gpt-4:150,text-embedding-3-small:100 --regions eastus2 --verbose +✔️ All parameters combined: + ```powershell + .\quota_check.ps1 -Models "gpt-4.1-mini:40" -Regions "eastus,westus" -CheckFabric -Verbose ``` ## **Sample Output** The final table lists regions with available quota. You can select any of these regions for deployment. -![quota-check-output](../img/Documentation/quota-check-output.png) +``` +╔══════════════════════════════════════════════════════════════╗ +║ QUOTA CHECK SUMMARY ║ +╚══════════════════════════════════════════════════════════════╝ + +Region gpt-4.1-mini text-embedding-3-large Status +────────────────────────────────────────────────────────────────────────────────────────── +eastus ✅ 200/240 (need 40) ✅ 120/200 (need 40) ✅ PASS +eastus2 ❌ 10/240 (need 40) ✅ 50/200 (need 40) ❌ FAIL +swedencentral ✅ 100/240 (need 40) ✅ 80/200 (need 40) ✅ PASS +``` --- ## **If using Azure Portal and Cloud Shell** @@ -74,22 +107,33 @@ The final table lists regions with available quota. You can select any of these chmod +x quota_check.sh ./quota_check.sh ``` - - Refer to [Input Formats](#input-formats) for detailed commands. + - Refer to [Input Formats — Bash](#input-formats--bash) for detailed commands. ## **If using VS Code or Codespaces** + +### Option 1: Bash (Linux, macOS, Git Bash, WSL, Cloud Shell) 1. Open the terminal in VS Code or Codespaces. -2. Use a terminal that can run bash. This is only for the quota check script; deployment uses PowerShell. +2. Use a terminal that can run bash. ![git_bash](../img/provisioning/git_bash.png) -3. Navigate to the `scripts` folder where the script files are located and make the script as executable: +3. Navigate to the `scripts` folder and make the script executable: ```sh cd scripts chmod +x quota_check.sh ``` -4. Run the appropriate script based on your requirement: - - **To check quota for the deployment** - +4. Run the script: ```sh ./quota_check.sh ``` - - Refer to [Input Formats](#input-formats) for detailed commands. \ No newline at end of file + - Refer to [Input Formats — Bash](#input-formats--bash) for detailed commands. + +### Option 2: PowerShell (Windows, Linux, macOS) +1. Open a PowerShell terminal in VS Code. +2. Navigate to the `scripts` folder: + ```powershell + cd scripts + ``` +3. Run the script: + ```powershell + .\quota_check.ps1 + ``` + - Refer to [Input Formats — PowerShell](#input-formats--powershell) for detailed commands. \ No newline at end of file diff --git a/scripts/quota_check.ps1 b/scripts/quota_check.ps1 new file mode 100644 index 0000000..209f1bf --- /dev/null +++ b/scripts/quota_check.ps1 @@ -0,0 +1,306 @@ +<# +.SYNOPSIS + Checks Azure OpenAI quota and (optionally) Fabric capacity availability across regions. + +.DESCRIPTION + Verifies that the Azure subscription has sufficient OpenAI model quota in each + candidate region for the models required by this accelerator. + + Default models (from infra/main.bicepparam): + gpt-4.1-mini GlobalStandard 40K TPM + text-embedding-3-large Standard 40K TPM + +.PARAMETER Models + Comma-separated model list. Format: name:capacity[:sku] + When sku is omitted it defaults to GlobalStandard. + Example: "gpt-4.1-mini:40:GlobalStandard,text-embedding-3-large:40:Standard" + +.PARAMETER Regions + Comma-separated Azure region list. + Example: "eastus,westus2,swedencentral" + +.PARAMETER CheckFabric + When set, also validates that the Fabric F8 SKU is available in each region. + +.PARAMETER Verbose + Enables detailed output. + +.EXAMPLE + .\quota_check.ps1 +.EXAMPLE + .\quota_check.ps1 -Models "gpt-4.1-mini:40" -Regions "eastus,westus" +.EXAMPLE + .\quota_check.ps1 -CheckFabric -Verbose +#> + +[CmdletBinding()] +param( + [string]$Models, + [string]$Regions, + [switch]$CheckFabric +) + +$ErrorActionPreference = 'Stop' + +# ---- Defaults ---- +$DefaultModels = 'gpt-4.1-mini:40:GlobalStandard,text-embedding-3-large:40:Standard' +$DefaultRegions = 'eastus,eastus2,swedencentral,uksouth,westus,westus2,southcentralus,canadacentral,australiaeast,japaneast,norwayeast' + +# ---- Resolve inputs ---- +function Resolve-ModelList { + param([string]$ModelString) + $result = @() + foreach ($entry in ($ModelString -split ',')) { + $entry = $entry.Trim() + if ([string]::IsNullOrWhiteSpace($entry)) { continue } + $parts = $entry -split ':' + if ($parts.Count -lt 2) { + Write-Error "Invalid model format: '$entry'. Expected name:capacity[:sku]" + exit 1 + } + $result += [PSCustomObject]@{ + Name = $parts[0] + Capacity = [int]$parts[1] + Sku = if ($parts.Count -ge 3) { $parts[2] } else { 'GlobalStandard' } + } + } + return $result +} + +$modelList = Resolve-ModelList -ModelString $(if ($Models) { $Models } else { $DefaultModels }) +$regionList = ($(if ($Regions) { $Regions } else { $DefaultRegions })) -split ',' | ForEach-Object { $_.Trim() } + +# ---- Auth check ---- +Write-Host '' +Write-Host '╔══════════════════════════════════════════════════════════════╗' -ForegroundColor Cyan +Write-Host '║ Deploy Your AI Application In Production - Quota Check ║' -ForegroundColor Cyan +Write-Host '╚══════════════════════════════════════════════════════════════╝' -ForegroundColor Cyan +Write-Host '' + +try { + $account = az account show --output json 2>$null | ConvertFrom-Json +} catch { + Write-Host '❌ Not logged into Azure CLI. Please run "az login" first.' -ForegroundColor Red + exit 1 +} + +if (-not $account) { + Write-Host '❌ Not logged into Azure CLI. Please run "az login" first.' -ForegroundColor Red + exit 1 +} + +$subscriptionName = $account.name +$subscriptionId = $account.id +Write-Host "🔑 Subscription: $subscriptionName ($subscriptionId)" +Write-Host '' + +# ---- Display config ---- +Write-Host '📋 Configuration:' -ForegroundColor Yellow +Write-Host ' Models:' +foreach ($m in $modelList) { + Write-Host " • $($m.Name) (SKU: $($m.Sku), Required capacity: $($m.Capacity)K TPM)" +} +Write-Host " Regions: $($regionList -join ', ')" +Write-Host " Check Fabric: $CheckFabric" +Write-Host " Verbose: $VerbosePreference" +Write-Host '' + +# ---- Results tracking ---- +$results = @() +$validRegions = @() + +# ---- Main quota check loop ---- +foreach ($region in $regionList) { + Write-Host '════════════════════════════════════════════════════════' -ForegroundColor DarkGray + Write-Host "🔍 Checking region: $region" -ForegroundColor White + + $quotaInfo = $null + try { + $quotaJson = az cognitiveservices usage list --location $region --output json 2>$null + if ($quotaJson) { + $quotaInfo = $quotaJson | ConvertFrom-Json + } + } catch { + # Swallow – region will be skipped + } + + if (-not $quotaInfo -or $quotaInfo.Count -eq 0) { + Write-Host ' ⚠️ Failed to retrieve quota info. Skipping.' -ForegroundColor DarkYellow + $regionResult = [PSCustomObject]@{ Region = $region; Status = 'SKIP'; Details = @{} } + $results += $regionResult + continue + } + + $allPass = $true + $details = @{} + + foreach ($m in $modelList) { + $quotaKey = "OpenAI.$($m.Sku).$($m.Name)" + $required = $m.Capacity + $displayName = "$($m.Name) ($($m.Sku))" + + $usage = $quotaInfo | Where-Object { $_.name.value -eq $quotaKey } + + # Azure quota keys for gpt-4.1 family omit the first hyphen (gpt4.1-mini not gpt-4.1-mini) + if (-not $usage -and $m.Name -match '^gpt-') { + $altName = $m.Name -replace '^gpt-', 'gpt' + $altKey = "OpenAI.$($m.Sku).$altName" + $usage = $quotaInfo | Where-Object { $_.name.value -eq $altKey } + if ($usage -and ($VerbosePreference -eq 'Continue')) { + Write-Host " (Matched via alternate key: $altKey)" -ForegroundColor DarkGray + } + } + + if (-not $usage) { + Write-Host " ⚠️ $displayName — No quota info found in $region" -ForegroundColor DarkYellow + if ($VerbosePreference -eq 'Continue') { + Write-Host " (Looked for quota key: $quotaKey)" -ForegroundColor DarkGray + } + $allPass = $false + $details[$m.Name] = [PSCustomObject]@{ Available = -1; Limit = -1; Status = 'N/A' } + continue + } + + $current = [int]$usage.currentValue + $limit = [int]$usage.limit + $available = $limit - $current + + if ($available -lt $required) { + Write-Host " ❌ $displayName | Used: $current | Limit: $limit | Available: $available | Need: $required" -ForegroundColor Red + $allPass = $false + $details[$m.Name] = [PSCustomObject]@{ Available = $available; Limit = $limit; Status = 'FAIL' } + } else { + Write-Host " ✅ $displayName | Used: $current | Limit: $limit | Available: $available | Need: $required" -ForegroundColor Green + $details[$m.Name] = [PSCustomObject]@{ Available = $available; Limit = $limit; Status = 'PASS' } + } + } + + # ---- Optional Fabric check ---- + if ($CheckFabric) { + $fabricSku = 'F8' + Write-Host " 🔍 Checking Fabric capacity ($fabricSku) availability..." -ForegroundColor White + try { + $skuJson = az rest ` + --method get ` + --url "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Fabric/skus?api-version=2023-11-01" ` + --output json 2>$null + $skus = ($skuJson | ConvertFrom-Json).value + + $match = $skus | Where-Object { $_.name -eq $fabricSku } + if ($match -and ($match.locations -contains $region -or $match.locations -match $region)) { + Write-Host " ✅ Fabric $fabricSku — Available in $region" -ForegroundColor Green + $details['Fabric'] = [PSCustomObject]@{ Available = 1; Limit = 1; Status = 'PASS' } + } else { + Write-Host " ⚠️ Fabric $fabricSku — Could not confirm availability in $region" -ForegroundColor DarkYellow + $details['Fabric'] = [PSCustomObject]@{ Available = 0; Limit = 0; Status = 'WARN' } + } + } catch { + Write-Host " ⚠️ Fabric check failed for $region" -ForegroundColor DarkYellow + $details['Fabric'] = [PSCustomObject]@{ Available = 0; Limit = 0; Status = 'WARN' } + } + } + + if ($allPass) { + $validRegions += $region + Write-Host " 🎉 Region '$region' has sufficient quota for all models!" -ForegroundColor Green + } + + $results += [PSCustomObject]@{ + Region = $region + Status = $(if ($allPass) { 'PASS' } else { 'FAIL' }) + Details = $details + } +} + +# ---- Summary table ---- +Write-Host '' +Write-Host '╔══════════════════════════════════════════════════════════════╗' -ForegroundColor Cyan +Write-Host '║ QUOTA CHECK SUMMARY ║' -ForegroundColor Cyan +Write-Host '╚══════════════════════════════════════════════════════════════╝' -ForegroundColor Cyan +Write-Host '' + +# Build header +$header = '{0,-22}' -f 'Region' +foreach ($m in $modelList) { + $header += '{0,-30}' -f $m.Name +} +if ($CheckFabric) { + $header += '{0,-16}' -f 'Fabric' +} +$header += '{0,-10}' -f 'Status' +Write-Host $header -ForegroundColor White + +$separatorLen = 22 + ($modelList.Count * 30) + 10 +if ($CheckFabric) { $separatorLen += 16 } +Write-Host ('─' * $separatorLen) -ForegroundColor DarkGray + +foreach ($r in $results) { + $line = '{0,-22}' -f $r.Region + + foreach ($m in $modelList) { + $d = $r.Details[$m.Name] + if ($null -eq $d -or $d.Status -eq 'N/A') { + $cell = '⚠️ N/A' + } elseif ($d.Status -eq 'PASS') { + $cell = "✅ $($d.Available)/$($d.Limit) (need $($m.Capacity))" + } else { + $cell = "❌ $($d.Available)/$($d.Limit) (need $($m.Capacity))" + } + $line += '{0,-30}' -f $cell + } + + if ($CheckFabric) { + $fd = $r.Details['Fabric'] + if (-not $fd) { + $line += '{0,-16}' -f '—' + } elseif ($fd.Status -eq 'PASS') { + $line += '{0,-16}' -f '✅ Available' + } else { + $line += '{0,-16}' -f '⚠️ Unknown' + } + } + + $statusStr = switch ($r.Status) { + 'PASS' { '✅ PASS' } + 'FAIL' { '❌ FAIL' } + 'SKIP' { '⚠️ SKIP' } + default { $r.Status } + } + $line += '{0,-10}' -f $statusStr + + $color = switch ($r.Status) { + 'PASS' { 'Green' } + 'FAIL' { 'Red' } + default { 'DarkYellow' } + } + Write-Host $line -ForegroundColor $color +} + +# ---- Final recommendation ---- +Write-Host '' +Write-Host '════════════════════════════════════════════════════════' -ForegroundColor DarkGray + +if ($validRegions.Count -eq 0) { + Write-Host '❌ No region found with sufficient quota for all models!' -ForegroundColor Red + Write-Host '' + Write-Host ' Recommendations:' -ForegroundColor Yellow + Write-Host ' 1. Request a quota increase via Azure Portal → Quotas' + Write-Host ' 2. Try different regions with the -Regions parameter' + Write-Host ' 3. Reduce model capacity requirements with the -Models parameter' + Write-Host '' + Write-Host ' Models needed:' + foreach ($m in $modelList) { + Write-Host " • $($m.Name) (SKU: $($m.Sku), Capacity: $($m.Capacity)K TPM)" + } + exit 1 +} else { + Write-Host '✅ Regions with sufficient quota:' -ForegroundColor Green + foreach ($r in $validRegions) { + Write-Host " • $r" -ForegroundColor Green + } + Write-Host '' + Write-Host ' To deploy, set your desired region:' -ForegroundColor White + Write-Host ' azd env set AZURE_LOCATION ' -ForegroundColor White + Write-Host ' azd up' -ForegroundColor White + exit 0 +} diff --git a/scripts/quota_check.sh b/scripts/quota_check.sh new file mode 100644 index 0000000..a481534 --- /dev/null +++ b/scripts/quota_check.sh @@ -0,0 +1,348 @@ +#!/bin/bash + +# ============================================================================= +# Quota Check Script for Deploy Your AI Application In Production +# ============================================================================= +# Checks Azure OpenAI quota and Fabric capacity availability across regions +# for the models required by this accelerator. +# +# No external dependencies beyond Azure CLI (az). Uses az --query (JMESPath) +# for JSON parsing instead of python3 or jq. +# +# Default models (from infra/main.bicepparam): +# gpt-4.1-mini:40 (GlobalStandard), text-embedding-3-large:40 (Standard) +# +# Default regions: +# eastus, eastus2, swedencentral, uksouth, westus, westus2, +# southcentralus, canadacentral, australiaeast, japaneast, norwayeast +# +# Usage: +# ./quota_check.sh +# ./quota_check.sh --verbose +# ./quota_check.sh --models gpt-4.1-mini:40,text-embedding-3-large:40 +# ./quota_check.sh --regions eastus,westus2 +# ./quota_check.sh --models gpt-4.1-mini:40 --regions eastus,westus --verbose +# ./quota_check.sh --check-fabric +# ============================================================================= + +set -euo pipefail + +# ---- Defaults ---- +DEFAULT_MODELS="gpt-4.1-mini:40:GlobalStandard,text-embedding-3-large:40:Standard" +DEFAULT_REGIONS="eastus,eastus2,swedencentral,uksouth,westus,westus2,southcentralus,canadacentral,australiaeast,japaneast,norwayeast" +VERBOSE=false +CHECK_FABRIC=false + +# ---- Parse arguments ---- +MODELS_INPUT="" +REGIONS_INPUT="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --models) + MODELS_INPUT="$2" + shift 2 + ;; + --regions) + REGIONS_INPUT="$2" + shift 2 + ;; + --verbose) + VERBOSE=true + shift + ;; + --check-fabric) + CHECK_FABRIC=true + shift + ;; + --help|-h) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --models MODEL_LIST Comma-separated models (format: name:capacity[:sku])" + echo " Default: $DEFAULT_MODELS" + echo " --regions REGION_LIST Comma-separated Azure regions" + echo " Default: $DEFAULT_REGIONS" + echo " --check-fabric Also check Microsoft Fabric capacity SKU availability" + echo " --verbose Enable detailed logging" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " $0" + echo " $0 --models gpt-4.1-mini:40,text-embedding-3-large:40 --regions eastus,westus" + echo " $0 --check-fabric --verbose" + exit 0 + ;; + *) + echo "❌ Unknown option: $1" + echo " Run '$0 --help' for usage." + exit 1 + ;; + esac +done + +# ---- Resolve models ---- +resolve_models() { + local input="$1" + local resolved=() + IFS=',' read -ra entries <<< "$input" + for entry in "${entries[@]}"; do + local name capacity sku + IFS=':' read -r name capacity sku <<< "$entry" + if [[ -z "$name" || -z "$capacity" ]]; then + echo "❌ Invalid model format: '$entry'. Expected name:capacity[:sku]" >&2 + exit 1 + fi + sku="${sku:-GlobalStandard}" + resolved+=("${name}:${capacity}:${sku}") + done + echo "${resolved[*]}" +} + +if [[ -n "$MODELS_INPUT" ]]; then + MODELS_RAW="$MODELS_INPUT" +else + MODELS_RAW="$DEFAULT_MODELS" +fi + +IFS=' ' read -ra MODELS <<< "$(resolve_models "$MODELS_RAW")" + +if [[ -n "$REGIONS_INPUT" ]]; then + IFS=',' read -ra REGIONS <<< "$REGIONS_INPUT" +else + IFS=',' read -ra REGIONS <<< "$DEFAULT_REGIONS" +fi + +# ---- Helper: query quota for a specific key in a region ---- +# Uses az CLI --query (JMESPath) — no python3/jq dependency. +# Returns "currentValue\tlimit" (tab-separated) or empty string. +query_quota() { + local region="$1" + local quota_key="$2" + az cognitiveservices usage list \ + --location "$region" \ + --query "[?name.value=='${quota_key}'].{c:currentValue,l:limit} | [0]" \ + --output tsv 2>/dev/null || echo "" +} + +# ---- Authentication check ---- +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ Deploy Your AI Application In Production - Quota Check ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +if ! az account show &>/dev/null; then + echo "❌ Not logged into Azure CLI. Please run 'az login' first." + exit 1 +fi + +SUBSCRIPTION_NAME=$(az account show --query "name" -o tsv 2>/dev/null) +SUBSCRIPTION_ID=$(az account show --query "id" -o tsv 2>/dev/null) +echo "🔑 Subscription: $SUBSCRIPTION_NAME ($SUBSCRIPTION_ID)" +echo "" + +# ---- Display configuration ---- +echo "📋 Configuration:" +echo " Models:" +for m in "${MODELS[@]}"; do + IFS=':' read -r mname mcap msku <<< "$m" + echo " • $mname (SKU: $msku, Required capacity: ${mcap}K TPM)" +done +echo " Regions: ${REGIONS[*]}" +echo " Check Fabric: $CHECK_FABRIC" +echo " Verbose: $VERBOSE" +echo "" + +# ---- Build model info arrays ---- +MODEL_NAMES=() +MODEL_CAPS=() +MODEL_SKUS=() +MODEL_PRIMARY_KEYS=() +MODEL_ALT_KEYS=() + +for m in "${MODELS[@]}"; do + IFS=':' read -r mname mcap msku <<< "$m" + MODEL_NAMES+=("$mname") + MODEL_CAPS+=("$mcap") + MODEL_SKUS+=("$msku") + MODEL_PRIMARY_KEYS+=("OpenAI.${msku}.${mname}") + # Azure quota keys for gpt-4.1 family omit the first hyphen (gpt4.1-mini not gpt-4.1-mini) + if [[ "$mname" == gpt-* ]]; then + alt_mname="${mname/gpt-/gpt}" + MODEL_ALT_KEYS+=("OpenAI.${msku}.${alt_mname}") + else + MODEL_ALT_KEYS+=("") + fi +done + +MODEL_COUNT=${#MODEL_NAMES[@]} + +# ---- Results tracking ---- +declare -A REGION_STATUS +VALID_REGIONS=() + +# ---- Main quota check loop ---- +for REGION in "${REGIONS[@]}"; do + echo "════════════════════════════════════════════════════════" + echo "🔍 Checking region: $REGION" + + ALL_PASS=true + safe_region="${REGION//[^a-zA-Z0-9]/_}" + + for ((i=0; i/dev/null || echo "") + + if echo "$SKU_CHECK" | grep -qi "$REGION" 2>/dev/null; then + echo " ✅ Fabric $FABRIC_SKU — Available in $REGION" + else + echo " ⚠️ Fabric $FABRIC_SKU — Could not confirm availability in $REGION" + if $VERBOSE; then + echo " (Fabric SKU availability check returned no match for $REGION)" + fi + fi + fi + + if $ALL_PASS; then + REGION_STATUS["$REGION"]="pass" + VALID_REGIONS+=("$REGION") + echo " 🎉 Region '$REGION' has sufficient quota for all models!" + else + REGION_STATUS["$REGION"]="fail" + fi +done + +# ---- Summary table ---- +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ QUOTA CHECK SUMMARY ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +printf "%-22s" "Region" +for ((i=0; i" + echo " azd up" + exit 0 +fi