diff --git a/azure.yaml b/azure.yaml index 5dc6faf..1171536 100644 --- a/azure.yaml +++ b/azure.yaml @@ -27,7 +27,7 @@ hooks: shell: sh run: > 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" - interactive: true + interactive: false continueOnError: false windows: @@ -35,5 +35,5 @@ hooks: run: > $location = if ($env:AZURE_AISERVICE_LOCATION) { $env:AZURE_AISERVICE_LOCATION } else { "japaneast" }; ./scripts/validate_model_deployment_quota.ps1 -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -Location $location -ModelsParameter "aiModelDeployments" - interactive: true + interactive: false continueOnError: false \ No newline at end of file diff --git a/scripts/validate_model_deployment_quota.ps1 b/scripts/validate_model_deployment_quota.ps1 index dda7297..08773d1 100644 --- a/scripts/validate_model_deployment_quota.ps1 +++ b/scripts/validate_model_deployment_quota.ps1 @@ -4,75 +4,73 @@ param ( [string]$ModelsParameter ) -$AiFoundryName = $env:AZURE_AIFOUNDRY_NAME +# Read from environment variables (do not pass in azure.yaml) +$AiServiceName = $env:AZURE_AISERVICE_NAME $ResourceGroup = $env:AZURE_RESOURCE_GROUP # Validate required parameters $MissingParams = @() + if (-not $SubscriptionId) { $MissingParams += "SubscriptionId" } if (-not $Location) { $MissingParams += "Location" } if (-not $ModelsParameter) { $MissingParams += "ModelsParameter" } if ($MissingParams.Count -gt 0) { Write-Error "❌ ERROR: Missing required parameters: $($MissingParams -join ', ')" - Write-Host "Usage: validate_model_deployment_quota.ps1 -SubscriptionId -Location -ModelsParameter " + Write-Host "Usage: .\validate_model_deployment_quotas.ps1 -SubscriptionId -Location -ModelsParameter " exit 1 } -# Load model deployments from parameter file +# Load main.parameters.json $JsonContent = Get-Content -Path "./infra/main.parameters.json" -Raw | ConvertFrom-Json -$aiModelDeployments = $JsonContent.parameters.$ModelsParameter.value -if (-not $aiModelDeployments -or -not ($aiModelDeployments -is [System.Collections.IEnumerable])) { - Write-Error "❌ ERROR: Failed to parse main.parameters.json or missing '$ModelsParameter'" +if (-not $JsonContent) { + Write-Error "❌ ERROR: Failed to parse main.parameters.json. Ensure the JSON file is valid." exit 1 } -# Try to discover AI Foundry name if not set -if (-not $AiFoundryName -and $ResourceGroup) { - $AiFoundryName = az cognitiveservices account list ` - --resource-group $ResourceGroup ` - --query "sort_by([?kind=='AIServices'], &name)[0].name" ` - -o tsv 2>$null +$aiModelDeployments = $JsonContent.parameters.$ModelsParameter.value +if (-not $aiModelDeployments -or -not ($aiModelDeployments -is [System.Collections.IEnumerable])) { + Write-Error "❌ ERROR: The specified property '$ModelsParameter' does not exist or is not an array." + exit 1 } -# Check if AI Foundry exists -if ($AiFoundryName -and $ResourceGroup) { +# Check if AI resource + all deployments already exist +if ($AiServiceName -and $ResourceGroup) { $existing = az cognitiveservices account show ` - --name $AiFoundryName ` + --name $AiServiceName ` --resource-group $ResourceGroup ` --query "name" --output tsv 2>$null if ($existing) { - # adding into .env - azd env set AZURE_AIFOUNDRY_NAME $existing | Out-Null - - $deployedModelsOutput = az cognitiveservices account deployment list ` - --name $AiFoundryName ` + $deployedModels = az cognitiveservices account deployment list ` + --name $AiServiceName ` --resource-group $ResourceGroup ` --query "[].name" --output tsv 2>$null - $deployedModels = @() - if ($deployedModelsOutput -is [string]) { - $deployedModels += $deployedModelsOutput - } elseif ($deployedModelsOutput) { - $deployedModels = $deployedModelsOutput -split "`r?`n" + $requiredDeployments = @() + foreach ($deployment in $aiModelDeployments) { + $requiredDeployments += $deployment.name } - $requiredDeployments = $aiModelDeployments | ForEach-Object { $_.name } - $missingDeployments = $requiredDeployments | Where-Object { $_ -notin $deployedModels } + $missingDeployments = @() + foreach ($required in $requiredDeployments) { + if ($deployedModels -notcontains $required) { + $missingDeployments += $required + } + } if ($missingDeployments.Count -eq 0) { - Write-Host "ℹ️ AI Foundry '$AiFoundryName' exists and all required model deployments are already provisioned." + Write-Host "ℹ️ Azure AI service '$AiServiceName' exists and all required model deployments are provisioned." Write-Host "⏭️ Skipping quota validation." exit 0 } else { - Write-Host "🔍 AI Foundry exists, but the following model deployments are missing: $($missingDeployments -join ', ')" + Write-Host "🔍 AI service exists, but the following model deployments are missing: $($missingDeployments -join ', ')" Write-Host "➡️ Proceeding with quota validation for missing models..." } } } -# Run quota validation +# Start quota validation az account set --subscription $SubscriptionId Write-Host "🎯 Active Subscription: $(az account show --query '[name, id]' --output tsv)" @@ -84,14 +82,13 @@ foreach ($deployment in $aiModelDeployments) { $type = if ($env:AZURE_ENV_MODEL_DEPLOYMENT_TYPE) { $env:AZURE_ENV_MODEL_DEPLOYMENT_TYPE } else { $deployment.sku.name } $capacity = if ($env:AZURE_ENV_MODEL_CAPACITY) { $env:AZURE_ENV_MODEL_CAPACITY } else { $deployment.sku.capacity } - Write-Host "" - Write-Host "🔍 Validating model deployment: $name ..." + Write-Host "`n🔍 Validating model deployment: $name ..." & .\scripts\validate_model_quota.ps1 -Location $Location -Model $model -Capacity $capacity -DeploymentType $type $exitCode = $LASTEXITCODE if ($exitCode -ne 0) { if ($exitCode -eq 2) { - exit 1 + exit 1 # already printed, graceful } Write-Error "❌ ERROR: Quota validation failed for model deployment: $name" $QuotaAvailable = $false @@ -99,9 +96,9 @@ foreach ($deployment in $aiModelDeployments) { } if (-not $QuotaAvailable) { - Write-Error "❌ ERROR: One or more model deployments failed quota validation." + Write-Error "❌ ERROR: One or more model deployments failed validation." exit 1 } else { Write-Host "✅ All model deployments passed quota validation successfully." exit 0 -} +} \ No newline at end of file diff --git a/scripts/validate_model_deployment_quota.sh b/scripts/validate_model_deployment_quota.sh index acf1e3b..bd098bd 100644 --- a/scripts/validate_model_deployment_quota.sh +++ b/scripts/validate_model_deployment_quota.sh @@ -25,9 +25,6 @@ while [[ $# -gt 0 ]]; do esac done -AIFOUNDRY_NAME="${AZURE_AIFOUNDRY_NAME}" -RESOURCE_GROUP="${AZURE_RESOURCE_GROUP}" - # Validate required parameters MISSING_PARAMS=() [[ -z "$SUBSCRIPTION_ID" ]] && MISSING_PARAMS+=("SubscriptionId") @@ -40,55 +37,51 @@ if [[ ${#MISSING_PARAMS[@]} -ne 0 ]]; then exit 1 fi -# Load model definitions -aiModelDeployments=$(jq -c ".parameters.$MODELS_PARAMETER.value[]" ./infra/main.parameters.json 2>/dev/null) -if [[ $? -ne 0 || -z "$aiModelDeployments" ]]; then - echo "❌ ERROR: Failed to parse main.parameters.json or missing '$MODELS_PARAMETER'" - exit 1 -fi - -# Try to discover AI Foundry name if not set -if [[ -z "$AIFOUNDRY_NAME" && -n "$RESOURCE_GROUP" ]]; then - AIFOUNDRY_NAME=$(az cognitiveservices account list --resource-group "$RESOURCE_GROUP" \ - --query "sort_by([?kind=='AIServices'], &name)[0].name" -o tsv 2>/dev/null) -fi - -# Check if AI Foundry exists -if [[ -n "$AIFOUNDRY_NAME" && -n "$RESOURCE_GROUP" ]]; then - existing=$(az cognitiveservices account show --name "$AIFOUNDRY_NAME" \ - --resource-group "$RESOURCE_GROUP" --query "name" --output tsv 2>/dev/null) +# Read from environment +AISERVICE_NAME="${AZURE_AISERVICE_NAME}" +RESOURCE_GROUP="${AZURE_RESOURCE_GROUP}" +# Check service and deployment existence +if [[ -n "$AISERVICE_NAME" && -n "$RESOURCE_GROUP" ]]; then + existing=$(az cognitiveservices account show --name "$AISERVICE_NAME" --resource-group "$RESOURCE_GROUP" --query "name" --output tsv 2>/dev/null) if [[ -n "$existing" ]]; then - # adding into .env - azd env set AZURE_AIFOUNDRY_NAME "$existing" > /dev/null + echo "ℹ️ Found Azure AI service: $AISERVICE_NAME" - # Check model deployments - existing_deployments=$(az cognitiveservices account deployment list \ - --name "$AIFOUNDRY_NAME" \ - --resource-group "$RESOURCE_GROUP" \ - --query "[].name" --output tsv 2>/dev/null) + existing_deployments=$(az cognitiveservices account deployment list --name "$AISERVICE_NAME" --resource-group "$RESOURCE_GROUP" --query "[].name" --output tsv 2>/dev/null) - required_models=$(jq -r ".parameters.$MODELS_PARAMETER.value[].name" ./infra/main.parameters.json) + # Extract required model names + required_models=$(jq -r ".parameters.$MODELS_PARAMETER.value[].name" ./infra/main.parameters.json 2>/dev/null) - missing_models=() + if [[ -z "$required_models" ]]; then + echo "❌ ERROR: Failed to extract required model names from main.parameters.json" + exit 1 + fi + + all_present=true for model in $required_models; do if ! grep -q -w "$model" <<< "$existing_deployments"; then - missing_models+=("$model") + all_present=false + break fi done - if [[ ${#missing_models[@]} -eq 0 ]]; then - echo "ℹ️ AI Foundry '$AIFOUNDRY_NAME' exists and all required model deployments are already provisioned." + if [[ "$all_present" == "true" ]]; then + echo "✅ All required model deployments already exist in AI service '$AISERVICE_NAME'." echo "⏭️ Skipping quota validation." exit 0 else - echo "🔍 AI Foundry exists, but the following model deployments are missing: ${missing_models[*]}" - echo "➡️ Proceeding with quota validation for missing models..." + echo "🔍 AI service exists but some model deployments are missing — proceeding with quota validation." fi fi fi -# Run quota validation +# If we reach here, continue with normal quota checks +aiModelDeployments=$(jq -c ".parameters.$MODELS_PARAMETER.value[]" ./infra/main.parameters.json) +if [[ $? -ne 0 ]]; then + echo "❌ ERROR: Failed to parse main.parameters.json. Ensure jq is installed and the JSON is valid." + exit 1 +fi + az account set --subscription "$SUBSCRIPTION_ID" echo "🎯 Active Subscription: $(az account show --query '[name, id]' --output tsv)" @@ -100,13 +93,13 @@ while IFS= read -r deployment; do type=${AZURE_ENV_MODEL_DEPLOYMENT_TYPE:-$(echo "$deployment" | jq -r '.sku.name')} capacity=${AZURE_ENV_MODEL_CAPACITY:-$(echo "$deployment" | jq -r '.sku.capacity')} - echo "" echo "🔍 Validating model deployment: $name ..." ./scripts/validate_model_quota.sh --location "$LOCATION" --model "$model" --capacity "$capacity" --deployment-type "$type" - exit_code=$? + exit_code=$? if [[ $exit_code -ne 0 ]]; then if [[ $exit_code -eq 2 ]]; then + # Quota validation handled inside script — stop immediately exit 1 fi echo "❌ ERROR: Quota validation failed for model deployment: $name" @@ -115,9 +108,9 @@ while IFS= read -r deployment; do done <<< "$(echo "$aiModelDeployments")" if [[ "$quotaAvailable" = false ]]; then - echo "❌ ERROR: One or more model deployments failed quota validation." + echo "❌ ERROR: One or more model deployments failed validation." exit 1 else echo "✅ All model deployments passed quota validation successfully." exit 0 -fi +fi \ No newline at end of file diff --git a/scripts/validate_model_quota.ps1 b/scripts/validate_model_quota.ps1 index e151628..0328e1f 100644 --- a/scripts/validate_model_quota.ps1 +++ b/scripts/validate_model_quota.ps1 @@ -5,199 +5,113 @@ param ( [int]$Capacity ) -$RECOMMENDED_TOKENS = 200 -$BicepParamsFile = "main.bicepparams" -$ParametersJsonFile = "./infra/main.parameters.json" -$PreferredRegions = @('australiaeast', 'eastus', 'eastus2', 'francecentral', 'japaneast', 'norwayeast', 'southindia', 'swedencentral', 'uksouth', 'westus', 'westus3') -$AllResults = @() -$RecommendedRegions = @() -$NotRecommendedRegions = @() -$EligibleFallbacks = @() - -# ------------------ Validate Inputs ------------------ +# Validate parameters $MissingParams = @() if (-not $Location) { $MissingParams += "location" } if (-not $Model) { $MissingParams += "model" } -if (-not $Capacity -or $Capacity -le 0) { $MissingParams += "capacity" } +if (-not $Capacity) { $MissingParams += "capacity" } +if (-not $DeploymentType) { $MissingParams += "deployment-type" } if ($MissingParams.Count -gt 0) { - Write-Error "❌ ERROR: Missing or invalid parameters: $($MissingParams -join ', ')" + Write-Error "❌ ERROR: Missing required parameters: $($MissingParams -join ', ')" Write-Host "Usage: .\validate_model_quota.ps1 -Location -Model -Capacity [-DeploymentType ]" exit 1 } if ($DeploymentType -ne "Standard" -and $DeploymentType -ne "GlobalStandard") { - Write-Error "❌ ERROR: Invalid deployment type: $DeploymentType. Allowed: 'Standard', 'GlobalStandard'" + Write-Error "❌ ERROR: Invalid deployment type: $DeploymentType. Allowed values are 'Standard' or 'GlobalStandard'." exit 1 } $ModelType = "OpenAI.$DeploymentType.$Model" +$PreferredRegions = @('australiaeast', 'eastus', 'eastus2', 'francecentral', 'japaneast', 'norwayeast', 'southindia', 'swedencentral', 'uksouth', 'westus', 'westus3') +$AllResults = @() function Check-Quota { - param ([string]$Region) + param ( + [string]$Region + ) try { - $ModelInfoRaw = az cognitiveservices usage list --location $Region --query "[?name.value=='$ModelType']" --output json 2>$null + $ModelInfoRaw = az cognitiveservices usage list --location $Region --query "[?name.value=='$ModelType']" --output json $ModelInfo = $ModelInfoRaw | ConvertFrom-Json - if (-not $ModelInfo -or $ModelInfo.Count -eq 0) { - return $null - } + if (-not $ModelInfo) { return } - $Current = [int]$ModelInfo[0].currentValue - $Limit = [int]$ModelInfo[0].limit - $Available = $Limit - $Current + $CurrentValue = ($ModelInfo | Where-Object { $_.name.value -eq $ModelType }).currentValue + $Limit = ($ModelInfo | Where-Object { $_.name.value -eq $ModelType }).limit - if ($Available -ge $RECOMMENDED_TOKENS) { - $script:RecommendedRegions += $Region - } else { - $script:NotRecommendedRegions += $Region - } + $CurrentValue = [int]($CurrentValue -replace '\.0+$', '') + $Limit = [int]($Limit -replace '\.0+$', '') + $Available = $Limit - $CurrentValue return [PSCustomObject]@{ Region = $Region Model = $ModelType Limit = $Limit - Used = $Current + Used = $CurrentValue Available = $Available } } catch { - return $null + return } } -function Show-Table { - Write-Host "`n--------------------------------------------------------------------------------------------------" - Write-Host "| No. | Region | Model Name | Limit | Used | Available |" - Write-Host "--------------------------------------------------------------------------------------------------" - $i = 1 - foreach ($entry in $AllResults | Where-Object { $_.Available -gt 50 }) { - Write-Host ("| {0,-3} | {1,-15} | {2,-35} | {3,-5} | {4,-5} | {5,-9} |" -f $i, $entry.Region, $entry.Model, $entry.Limit, $entry.Used, $entry.Available) - $i++ - } - Write-Host "--------------------------------------------------------------------------------------------------" -} - -function Set-DeploymentValues($Region, $Capacity) { - azd env set AZURE_AISERVICE_LOCATION "$Region" | Out-Null - azd env set AZURE_ENV_MODEL_CAPACITY "$Capacity" | Out-Null - - if (Test-Path $ParametersJsonFile) { - try { - $json = Get-Content $ParametersJsonFile -Raw | ConvertFrom-Json - if ($json.parameters.aiModelDeployments.value.Count -gt 0) { - $json.parameters.aiModelDeployments.value[0].sku.capacity = $Capacity - $json | ConvertTo-Json -Depth 20 | Set-Content $ParametersJsonFile -Force - Write-Host "✅ Updated '$ParametersJsonFile' with capacity $Capacity." - } else { - Write-Host "⚠️ 'aiModelDeployments.value' array is empty. No changes made." - } - } catch { - Write-Host "❌ Failed to update '$ParametersJsonFile': $_" - } - } else { - Write-Host "⚠️ '$ParametersJsonFile' not found. Skipping update." - } -} - -# ------------------ Check Primary Region ------------------ +# First, check the user-specified region Write-Host "`n🔍 Checking quota in the requested region '$Location'..." $PrimaryResult = Check-Quota -Region $Location if ($PrimaryResult) { $AllResults += $PrimaryResult if ($PrimaryResult.Available -ge $Capacity) { - if ($RecommendedRegions -notcontains $Location -and $RecommendedRegions.Count -gt 0) { - Write-Host "`n⚠️ Selected region '$Location' has sufficient quota but is not among the recommended regions (≥ $RECOMMENDED_TOKENS tokens)." - Write-Host "🚨 Your application may not work as expected due to limited quota." - Write-Host "`nℹ️ Recommended regions: $($RecommendedRegions -join ', ')" - Write-Host "👉 It's advisable to deploy in one of these regions for optimal app performance." - $choice = Read-Host "❓ Do you want to choose a recommended region instead? (y/n)" - if ($choice -match "^[Yy]$") { - Show-Table - break - } else { - if ($Capacity -gt 200) { - Write-Host "`n⚠️ Reducing capacity to 200 in '$BicepParamsFile' for safer deployment..." - (Get-Content $BicepParamsFile) -replace "capacity\s*:\s*\d+", "capacity: 200" | Set-Content $BicepParamsFile - Write-Host "✅ Updated '$BicepParamsFile' with capacity 200." - } - Set-DeploymentValues $Location $Capacity - Write-Host "✅ Proceeding with '$Location' as selected." - exit 0 - } - } else { - Write-Host "`n✅ Sufficient quota found in original region '$Location'." - Set-DeploymentValues $Location $Capacity - exit 0 - } + Write-Host "`n✅ Sufficient quota found in original region '$Location'." + exit 0 } else { - Write-Host "`n⚠️ Insufficient quota in '$Location'. Checking fallback regions..." + Write-Host "`n⚠️ Insufficient quota in '$Location' (Available: $($PrimaryResult.Available), Required: $Capacity). Checking fallback regions..." } } else { - Write-Host "`n⚠️ Could not retrieve quota info for region '$Location'." + Write-Host "`n⚠️ Could not retrieve quota info for region '$Location'. Checking fallback regions..." } -# ------------------ Check Fallback Regions ------------------ -foreach ($region in $PreferredRegions) { - if ($region -eq $Location) { continue } +# Remove primary region from fallback list +$FallbackRegions = $PreferredRegions | Where-Object { $_ -ne $Location } + +foreach ($region in $FallbackRegions) { $result = Check-Quota -Region $region if ($result) { $AllResults += $result - if ($result.Available -ge $Capacity) { - $EligibleFallbacks += $region - } } } -Show-Table +# Display Results Table +Write-Host "`n-------------------------------------------------------------------------------------------------------------" +Write-Host "| No. | Region | Model Name | Limit | Used | Available |" +Write-Host "-------------------------------------------------------------------------------------------------------------" -if ($EligibleFallbacks.Count -gt 0) { - Write-Host "`n➡️ Found fallback regions with sufficient quota." - if ($RecommendedRegions.Count -gt 0) { - Write-Host "`nℹ️ Recommended regions (≥ $RECOMMENDED_TOKENS tokens available):" - foreach ($region in $RecommendedRegions) { - Write-Host " - $region" - } - } +$count = 1 +foreach ($entry in $AllResults) { + $modelShort = $entry.Model.Substring($entry.Model.LastIndexOf(".") + 1) + 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) + $count++ } +Write-Host "-------------------------------------------------------------------------------------------------------------" -# ------------------ Manual Prompt if No Quota Found ------------------ -Write-Host "`n❌ ERROR: No region has sufficient quota." - -while ($true) { - $ManualRegion = Read-Host "`nPlease enter a region you want to try manually" - if (-not $ManualRegion) { - Write-Host "❌ ERROR: No region entered. Exiting." - exit 1 - } - - $ManualCapacityStr = Read-Host "Enter the capacity you want to use (numeric value)" - if (-not ($ManualCapacityStr -as [int]) -or [int]$ManualCapacityStr -le 0) { - Write-Host "❌ Invalid capacity value. Try again." - continue - } +# Suggest fallback regions +$EligibleFallbacks = $AllResults | Where-Object { $_.Region -ne $Location -and $_.Available -ge $Capacity } - $ManualCapacity = [int]$ManualCapacityStr - $ManualResult = Check-Quota -Region $ManualRegion - - if (-not $ManualResult) { - Write-Host "⚠️ Could not retrieve quota info for region '$ManualRegion'. Try again." - continue +if ($EligibleFallbacks.Count -gt 0) { + Write-Host "`n❌ Deployment cannot proceed in '$Location'." + Write-Host "➡️ You can retry using one of the following regions with sufficient quota:`n" + foreach ($region in $EligibleFallbacks) { + Write-Host " • $($region.Region) (Available: $($region.Available))" } - if ($ManualResult.Available -ge $ManualCapacity) { - if ($ManualResult.Available -lt $RECOMMENDED_TOKENS) { - Write-Host "`n⚠️ Region '$ManualRegion' has less than recommended ($RECOMMENDED_TOKENS) tokens." - $proceed = Read-Host "❓ Proceed anyway? (y/n)" - if ($proceed -notmatch "^[Yy]$") { - continue - } - } - - Set-DeploymentValues $ManualRegion $ManualCapacity - Write-Host "✅ Deployment values set. Exiting." - exit 0 - } else { - Write-Host "❌ Quota in region '$ManualRegion' is insufficient. Available: $($ManualResult.Available), Required: $ManualCapacity" - } + Write-Host "`n🔧 To proceed, run:" + Write-Host " azd env set AZURE_AISERVICE_LOCATION ''" + Write-Host "📌 To confirm it's set correctly, run:" + Write-Host " azd env get-value AZURE_AISERVICE_LOCATION" + Write-Host "▶️ Once confirmed, re-run azd up to deploy the model in the new region." + exit 2 } + +Write-Error "`n❌ ERROR: No available quota found in any region." +exit 1 \ No newline at end of file diff --git a/scripts/validate_model_quota.sh b/scripts/validate_model_quota.sh index b1ca2bf..c194c53 100644 --- a/scripts/validate_model_quota.sh +++ b/scripts/validate_model_quota.sh @@ -4,67 +4,80 @@ LOCATION="" MODEL="" DEPLOYMENT_TYPE="Standard" CAPACITY=0 -RECOMMENDED_TOKENS=200 ALL_REGIONS=('australiaeast' 'eastus' 'eastus2' 'francecentral' 'japaneast' 'norwayeast' 'southindia' 'swedencentral' 'uksouth' 'westus' 'westus3') -# Globals for recommended/not recommended regions -RECOMMENDED_REGIONS=() -NOT_RECOMMENDED_REGIONS=() -ALL_RESULTS=() -FALLBACK_RESULTS=() - -# -------------------- Utility: Update .env and main.parameters.json -------------------- -update_env_and_parameters() { - local new_location="$1" - local new_capacity="$2" +# -------------------- Parse Args -------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --model) + MODEL="$2" + shift 2 + ;; + --capacity) + CAPACITY="$2" + shift 2 + ;; + --deployment-type) + DEPLOYMENT_TYPE="$2" + shift 2 + ;; + --location) + LOCATION="$2" + shift 2 + ;; + *) + echo "❌ ERROR: Unknown option: $1" + exit 1 + ;; + esac +done - echo "➡️ Updating environment and parameters with Location='$new_location' and Capacity='$new_capacity'..." +# -------------------- Validate Inputs -------------------- +MISSING_PARAMS=() +[[ -z "$LOCATION" ]] && MISSING_PARAMS+=("location") +[[ -z "$MODEL" ]] && MISSING_PARAMS+=("model") +[[ "$CAPACITY" -le 0 ]] && MISSING_PARAMS+=("capacity") - # Update the AZD environment - azd env set AZURE_AISERVICE_LOCATION "$new_location" - azd env set AZURE_ENV_MODEL_CAPACITY "$new_capacity" +if [[ ${#MISSING_PARAMS[@]} -ne 0 ]]; then + echo "❌ ERROR: Missing or invalid parameters: ${MISSING_PARAMS[*]}" + echo "Usage: $0 --location --model --capacity [--deployment-type ]" + exit 1 +fi - # Update main.parameters.json - local PARAM_FILE="./infra/main.parameters.json" - if [[ ! -f "$PARAM_FILE" ]]; then - echo "❌ ERROR: $PARAM_FILE not found, cannot update parameters." - return 1 - fi +if [[ "$DEPLOYMENT_TYPE" != "Standard" && "$DEPLOYMENT_TYPE" != "GlobalStandard" ]]; then + echo "❌ ERROR: Invalid deployment type: $DEPLOYMENT_TYPE. Allowed values: 'Standard', 'GlobalStandard'." + exit 1 +fi - jq --arg loc "$new_location" \ - '.parameters.location.value = $loc' "$PARAM_FILE" > "${PARAM_FILE}.tmp" && mv "${PARAM_FILE}.tmp" "$PARAM_FILE" +MODEL_TYPE="OpenAI.$DEPLOYMENT_TYPE.$MODEL" +ALL_RESULTS=() +FALLBACK_RESULTS=() +ROW_NO=1 - jq --argjson cap "$new_capacity" --arg model "$MODEL" \ - '(.parameters.aiModelDeployments.value[] | select(.name == $model) | .sku.capacity) |= $cap' "$PARAM_FILE" > "${PARAM_FILE}.tmp" && mv "${PARAM_FILE}.tmp" "$PARAM_FILE" +# Print validating message only once +echo -e "\n🔍 Validating model deployment: $MODEL ..." - echo "✅ Updated .env and $PARAM_FILE successfully." -} +echo "🔍 Checking quota in the requested region '$LOCATION'..." # -------------------- Function: Check Quota -------------------- check_quota() { local region="$1" - local MODEL_TYPE="OpenAI.$DEPLOYMENT_TYPE.$MODEL" local output - output=$(az cognitiveservices usage list --location "$region" --query "[?name.value=='$MODEL_TYPE']" --output json 2>/dev/null) if [[ -z "$output" || "$output" == "[]" ]]; then return 2 # No data fi - local CURRENT_VALUE=$(echo "$output" | jq -r '.[0].currentValue // 0' | cut -d'.' -f1) - local LIMIT=$(echo "$output" | jq -r '.[0].limit // 0' | cut -d'.' -f1) + local CURRENT_VALUE + local LIMIT + CURRENT_VALUE=$(echo "$output" | jq -r '.[0].currentValue // 0' | cut -d'.' -f1) + LIMIT=$(echo "$output" | jq -r '.[0].limit // 0' | cut -d'.' -f1) local AVAILABLE=$((LIMIT - CURRENT_VALUE)) ALL_RESULTS+=("$region|$LIMIT|$CURRENT_VALUE|$AVAILABLE") - if [[ "$AVAILABLE" -ge "$RECOMMENDED_TOKENS" ]]; then - RECOMMENDED_REGIONS+=("$region") - else - NOT_RECOMMENDED_REGIONS+=("$region") - fi - if [[ "$AVAILABLE" -ge "$CAPACITY" ]]; then return 0 else @@ -72,175 +85,71 @@ check_quota() { fi } -# -------------------- Input Validation -------------------- -while [[ $# -gt 0 ]]; do - case "$1" in - --model) - MODEL="$2"; shift 2 ;; - --capacity) - CAPACITY="$2"; shift 2 ;; - --deployment-type) - DEPLOYMENT_TYPE="$2"; shift 2 ;; - --location) - LOCATION="$2"; shift 2 ;; - *) - echo "❌ ERROR: Unknown option: $1"; exit 1 ;; - esac -done - -[[ -z "$LOCATION" ]] && MISSING_PARAMS+=("location") -[[ -z "$MODEL" ]] && MISSING_PARAMS+=("model") -if ! [[ "$CAPACITY" =~ ^[0-9]+$ ]] || [[ "$CAPACITY" -le 0 ]]; then - MISSING_PARAMS+=("capacity") -fi - -if [[ ${#MISSING_PARAMS[@]} -ne 0 ]]; then - echo "❌ ERROR: Missing/invalid: ${MISSING_PARAMS[*]}" - echo "Usage: $0 --location --model --capacity [--deployment-type ]" - exit 1 -fi +# -------------------- Check User-Specified Region -------------------- +check_quota "$LOCATION" +primary_status=$? -if [[ "$DEPLOYMENT_TYPE" != "Standard" && "$DEPLOYMENT_TYPE" != "GlobalStandard" ]]; then - echo "❌ ERROR: Invalid deployment type: $DEPLOYMENT_TYPE" +if [[ $primary_status -eq 2 ]]; then + echo -e "\n⚠️ Could not retrieve quota info for region: '$LOCATION'." exit 1 fi -# -------------------- Main Logic Starts -------------------- -echo "🔍 Checking quota in '$LOCATION' for model '$MODEL'..." - -check_quota "$LOCATION" -primary_status=$? - if [[ $primary_status -eq 1 ]]; then + # Get available quota from ALL_RESULTS for LOCATION to use in warning primary_entry="${ALL_RESULTS[0]}" IFS='|' read -r _ limit used available <<< "$primary_entry" echo -e "\n⚠️ Insufficient quota in '$LOCATION' (Available: $available, Required: $CAPACITY). Checking fallback regions..." fi +# -------------------- Check Fallback Regions -------------------- for region in "${ALL_REGIONS[@]}"; do [[ "$region" == "$LOCATION" ]] && continue check_quota "$region" - [[ $? -eq 0 ]] && FALLBACK_RESULTS+=("$region") + if [[ $? -eq 0 ]]; then + FALLBACK_RESULTS+=("$region") + fi done -# -------------------- Quota Table Output -------------------- +# -------------------- Print Results Table -------------------- echo "" -printf "%-5s | %-16s | %-33s | %-6s | %-6s | %-9s\n" "No." "Region" "Model Name" "Limit" "Used" "Available" -printf -- "---------------------------------------------------------------------------------------------\n" +printf "%-6s | %-18s | %-35s | %-8s | %-8s | %-9s\n" "No." "Region" "Model Name" "Limit" "Used" "Available" +printf -- "-------------------------------------------------------------------------------------------------------------\n" index=1 -REGIONS_WITH_QUOTA=() for result in "${ALL_RESULTS[@]}"; do IFS='|' read -r region limit used available <<< "$result" - if (( available >= 50 )); then - printf "| %-3s | %-16s | %-33s | %-6s | %-6s | %-9s |\n" "$index" "$region" "OpenAI.$DEPLOYMENT_TYPE.$MODEL" "$limit" "$used" "$available" - REGIONS_WITH_QUOTA+=("$region|$available") - ((index++)) - fi + printf "| %-4s | %-16s | %-33s | %-7s | %-7s | %-9s |\n" "$index" "$region" "$MODEL_TYPE" "$limit" "$used" "$available" + ((index++)) done -printf -- "---------------------------------------------------------------------------------------------\n" - -# -------------------- Prompt if No Region Has Enough -------------------- -if [[ $primary_status -ne 0 && ${#FALLBACK_RESULTS[@]} -eq 0 ]]; then - echo -e "\n❌ No region has sufficient quota (≥ $CAPACITY tokens)." - - max_available=0; max_region="" - for result in "${ALL_RESULTS[@]}"; do - IFS='|' read -r region limit used available <<< "$result" - if (( available > max_available )); then - max_available=$available - max_region=$region - fi - done - - if (( max_available == 0 )); then - echo "⚠️ No quota info from any region. Cannot proceed." - exit 1 - fi - - echo "➡️ Highest available quota: $max_available tokens in '$max_region'." - echo -n "❓ Enter new capacity to use (<= $max_available): " - read -r new_capacity < /dev/tty - - if ! [[ "$new_capacity" =~ ^[0-9]+$ ]] || (( new_capacity > max_available )) || (( new_capacity <= 0 )); then - echo "❌ Invalid capacity entered. Exiting." - exit 1 - fi - - echo -n "❓ Enter location to use (default: $max_region): " - read -r new_location < /dev/tty - new_location="${new_location:-$max_region}" - - CAPACITY=$new_capacity - LOCATION=$new_location +printf -- "-------------------------------------------------------------------------------------------------------------\n" - check_quota "$LOCATION" - [[ $? -eq 0 ]] || { echo "❌ Insufficient quota in '$LOCATION'. Exiting."; exit 1; } - - update_env_and_parameters "$LOCATION" "$CAPACITY" - echo "✅ Deployment settings updated." +# -------------------- Output Result -------------------- +if [[ $primary_status -eq 0 ]]; then + echo -e "\n✅ Sufficient quota found in original region '$LOCATION'." exit 0 fi -# -------------------- Handle Fallback Prompt -------------------- -ask_for_location() { - echo -e "\nPlease choose a region from the above list:" - echo -n "📍 Enter region: " - read -r new_location < /dev/tty - - if [[ -z "$new_location" ]]; then - echo "❌ ERROR: No location entered. Exiting." - exit 1 - fi - - echo -n "🔢 Enter capacity (tokens): " - read -r new_capacity < /dev/tty - - if ! [[ "$new_capacity" =~ ^[0-9]+$ ]] || (( new_capacity <= 0 )); then - echo "❌ Invalid capacity entered." - ask_for_location - return - fi - - CAPACITY=$new_capacity - LOCATION=$new_location - - check_quota "$LOCATION" - if [[ $? -eq 0 ]]; then - update_env_and_parameters "$LOCATION" "$CAPACITY" - echo "✅ Updated and ready to deploy in '$LOCATION'." - exit 0 - else - echo "❌ Insufficient quota in '$LOCATION'. Try another." - ask_for_location - fi -} +if [[ ${#FALLBACK_RESULTS[@]} -gt 0 ]]; then + echo -e "\n❌ Deployment cannot proceed in '$LOCATION'." + echo "➡️ You can retry using one of the following regions with sufficient quota:" + echo "" + for region in "${FALLBACK_RESULTS[@]}"; do + for result in "${ALL_RESULTS[@]}"; do + IFS='|' read -r rgn _ _ avail <<< "$result" + if [[ "$rgn" == "$region" ]]; then + echo " • $region (Available: $avail)" + break + fi + done + done -# -------------------- Final Decision Logic -------------------- -if [[ $primary_status -eq 0 ]]; then - - if [[ " ${NOT_RECOMMENDED_REGIONS[*]} " == *" $LOCATION "* ]]; then - recommended_list=$(IFS=, ; echo "${RECOMMENDED_REGIONS[*]}") - bold_regions=$(printf "\033[1m%s\033[0m" "$recommended_list") - echo -e "\n⚠️ \033[1mWarning:\033[0m Region '$LOCATION' has available tokens less than the recommended threshold ($RECOMMENDED_TOKENS)." - echo -e "🚨 Your application may not work as expected due to limited quota." - echo -e "\nℹ️ Recommended regions (≥ $RECOMMENDED_TOKENS tokens available): $bold_regions" - echo -e "👉 It's advisable to deploy in one of these regions for optimal app performance." - - echo -n "❓ Proceed anyway? (y/n): " - read -r proceed < /dev/tty - if [[ "$proceed" =~ ^[Yy]$ ]]; then - update_env_and_parameters "$LOCATION" "$CAPACITY" - echo "✅ Proceeding with '$LOCATION'." - exit 0 - else - ask_for_location - fi - else - update_env_and_parameters "$LOCATION" "$CAPACITY" - echo "✅ Quota is sufficient in '$LOCATION'. Proceeding." - exit 0 - fi -else - ask_for_location + echo -e "\n🔧 To proceed, run:" + echo " azd env set AZURE_AISERVICE_LOCATION ''" + echo "📌 To confirm it's set correctly, run:" + echo " azd env get-value AZURE_AISERVICE_LOCATION" + echo "▶️ Once confirmed, re-run azd up to deploy the model in the new region." + exit 2 fi + +echo -e "\n❌ ERROR: No available quota found in any region." +exit 1 \ No newline at end of file