Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion azure.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
name: multi-agent-custom-automation-engine-solution-accelerator
metadata:
template: [email protected]
template: [email protected]
hooks:
preprovision:
posix:
shell: sh
run: >
./infra/scripts/validate_model_deployment_quota.sh --subscription "$AZURE_SUBSCRIPTION_ID" --location "${AZURE_ENV_OPENAI_LOCATION:-swedencentral}" --models-parameter "aiModelDeployments"
interactive: false
continueOnError: false

windows:
shell: pwsh
run: >
$location = if ($env:AZURE_ENV_OPENAI_LOCATION) { $env:AZURE_ENV_OPENAI_LOCATION } else { "swedencentral" };
./infra/scripts/validate_model_deployment_quotas.ps1 -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -Location $location -ModelsParameter "aiModelDeployments"
interactive: false
continueOnError: false
75 changes: 75 additions & 0 deletions infra/main.parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"aiModelDeployments": {
"value": [
{
"name": "gpt",
"model": {
"name": "gpt-4o",
"version": "2024-08-06",
"format": "OpenAI"
},
"sku": {
"name": "GlobalStandard",
"capacity": 140
}
}
]
},
"environmentName": {
"value": "${AZURE_ENV_NAME}"
},
"location": {
"value": "${AZURE_LOCATION}"
},
"backendExists": {
"value": "${SERVICE_BACKEND_RESOURCE_EXISTS=false}"
},
"backendDefinition": {
"value": {
"settings": [
{
"name": "",
"value": "${VAR}",
"_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
"_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR} to use the value of 'VAR' from the current environment."
},
{
"name": "",
"value": "${VAR_S}",
"secret": true,
"_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
"_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR_S} to use the value of 'VAR_S' from the current environment."
}
]
}
},
"frontendExists": {
"value": "${SERVICE_FRONTEND_RESOURCE_EXISTS=false}"
},
"frontendDefinition": {
"value": {
"settings": [
{
"name": "",
"value": "${VAR}",
"_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
"_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR} to use the value of 'VAR' from the current environment."
},
{
"name": "",
"value": "${VAR_S}",
"secret": true,
"_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
"_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR_S} to use the value of 'VAR_S' from the current environment."
}
]
}
},
"principalId": {
"value": "${AZURE_PRINCIPAL_ID}"
}
}
}
2 changes: 1 addition & 1 deletion infra/scripts/quota_check_params.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ az account set --subscription "$AZURE_SUBSCRIPTION_ID"
echo "🎯 Active Subscription: $(az account show --query '[name, id]' --output tsv)"

# Default Regions to check (Comma-separated, now configurable)
DEFAULT_REGIONS="eastus,uksouth,eastus2,northcentralus,swedencentral,westus,westus2,southcentralus,canadacentral"
DEFAULT_REGIONS="australiaeast,eastus2,francecentral,japaneast,norwayeast,swedencentral,uksouth,westus"
IFS=',' read -r -a DEFAULT_REGION_ARRAY <<< "$DEFAULT_REGIONS"

# Read parameters (if any)
Expand Down
88 changes: 88 additions & 0 deletions infra/scripts/validate_model_deployment_quota.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/bash

SUBSCRIPTION_ID=""
LOCATION=""
MODELS_PARAMETER=""

while [[ $# -gt 0 ]]; do
case "$1" in
--subscription)
SUBSCRIPTION_ID="$2"
shift 2
;;
--location)
LOCATION="$2"
shift 2
;;
--models-parameter)
MODELS_PARAMETER="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done

# Verify all required parameters are provided and echo missing ones
MISSING_PARAMS=()

if [[ -z "$SUBSCRIPTION_ID" ]]; then
MISSING_PARAMS+=("subscription")
fi

if [[ -z "$LOCATION" ]]; then
MISSING_PARAMS+=("location")
fi

if [[ -z "$MODELS_PARAMETER" ]]; then
MISSING_PARAMS+=("models-parameter")
fi

if [[ ${#MISSING_PARAMS[@]} -ne 0 ]]; then
echo "❌ ERROR: Missing required parameters: ${MISSING_PARAMS[*]}"
echo "Usage: $0 --subscription <SUBSCRIPTION_ID> --location <LOCATION> --models-parameter <MODELS_PARAMETER>"
exit 1
fi

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 file is valid."
exit 1
fi

az account set --subscription "$SUBSCRIPTION_ID"
echo "🎯 Active Subscription: $(az account show --query '[name, id]' --output tsv)"

quotaAvailable=true

while IFS= read -r deployment; do
name=$(echo "$deployment" | jq -r '.name')
model=$(echo "$deployment" | jq -r '.model.name')
type=$(echo "$deployment" | jq -r '.sku.name')
capacity=$(echo "$deployment" | jq -r '.sku.capacity')

echo "🔍 Validating model deployment: $name ..."
./infra/scripts/validate_model_quota.sh --location "$LOCATION" --model "$model" --capacity $capacity --deployment-type $type

# Check if the script failed
exit_code=$?
if [ $exit_code -ne 0 ]; then
if [ $exit_code -eq 2 ]; then
# Skip printing any quota validation error — already handled inside the validation script
exit 1
fi
echo "❌ ERROR: Quota validation failed for model deployment: $name"
quotaAvailable=false
fi
done <<< "$(echo "$aiModelDeployments")"

if [ "$quotaAvailable" = false ]; then
echo "❌ ERROR: One or more model deployments failed validation."
exit 1
else
echo "✅ All model deployments passed quota validation successfully."
exit 0
fi
75 changes: 75 additions & 0 deletions infra/scripts/validate_model_deployment_quotas.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
param (
[string]$SubscriptionId,
[string]$Location,
[string]$ModelsParameter
)

# Verify all required parameters are provided
$MissingParams = @()

if (-not $SubscriptionId) {
$MissingParams += "subscription"
}

if (-not $Location) {
$MissingParams += "location"
}

if (-not $ModelsParameter) {
$MissingParams += "models-parameter"
}

if ($MissingParams.Count -gt 0) {
Write-Error "❌ ERROR: Missing required parameters: $($MissingParams -join ', ')"
Write-Host "Usage: .\validate_model_deployment_quotas.ps1 -SubscriptionId <SUBSCRIPTION_ID> -Location <LOCATION> -ModelsParameter <MODELS_PARAMETER>"
exit 1
}

$JsonContent = Get-Content -Path "./infra/main.parameters.json" -Raw | ConvertFrom-Json

if (-not $JsonContent) {
Write-Error "❌ ERROR: Failed to parse main.parameters.json. Ensure the JSON file is valid."
exit 1
}

$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
}

az account set --subscription $SubscriptionId
Write-Host "🎯 Active Subscription: $(az account show --query '[name, id]' --output tsv)"

$QuotaAvailable = $true

foreach ($deployment in $aiModelDeployments) {
$name = $deployment.name
$model = $deployment.model.name
$type = $deployment.sku.name
$capacity = $deployment.sku.capacity

Write-Host "🔍 Validating model deployment: $name ..."
& .\infra\scripts\validate_model_quota.ps1 -Location $Location -Model $model -Capacity $capacity -DeploymentType $type

# Check if the script failed
$exitCode = $LASTEXITCODE

if ($exitCode -ne 0) {
if ($exitCode -eq 2) {
# Quota error already printed inside the script, exit gracefully without reprinting
exit 1
}
Write-Error "❌ ERROR: Quota validation failed for model deployment: $name"
$QuotaAvailable = $false
}
}

if (-not $QuotaAvailable) {
Write-Error "❌ ERROR: One or more model deployments failed validation."
exit 1
} else {
Write-Host "✅ All model deployments passed quota validation successfully."
exit 0
}
108 changes: 108 additions & 0 deletions infra/scripts/validate_model_quota.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
param (
[string]$Location,
[string]$Model,
[string]$DeploymentType = "Standard",
[int]$Capacity
)

# Verify required parameters
$MissingParams = @()
if (-not $Location) { $MissingParams += "location" }
if (-not $Model) { $MissingParams += "model" }
if (-not $Capacity) { $MissingParams += "capacity" }
if (-not $DeploymentType) { $MissingParams += "deployment-type" }

if ($MissingParams.Count -gt 0) {
Write-Error "❌ ERROR: Missing required parameters: $($MissingParams -join ', ')"
Write-Host "Usage: .\validate_model_quota.ps1 -Location <LOCATION> -Model <MODEL> -Capacity <CAPACITY> [-DeploymentType <DEPLOYMENT_TYPE>]"
exit 1
}

if ($DeploymentType -ne "Standard" -and $DeploymentType -ne "GlobalStandard") {
Write-Error "❌ ERROR: Invalid deployment type: $DeploymentType. Allowed values are 'Standard' or 'GlobalStandard'."
exit 1
}

$ModelType = "OpenAI.$DeploymentType.$Model"

$PreferredRegions = @('australiaeast', 'eastus2', 'francecentral', 'japaneast', 'norwayeast', 'swedencentral', 'uksouth', 'westus')
$AllResults = @()

function Check-Quota {
param (
[string]$Region
)

$ModelInfoRaw = az cognitiveservices usage list --location $Region --query "[?name.value=='$ModelType']" --output json
$ModelInfo = $null

try {
$ModelInfo = $ModelInfoRaw | ConvertFrom-Json
} catch {
return
}

if (-not $ModelInfo) {
return
}

$CurrentValue = ($ModelInfo | Where-Object { $_.name.value -eq $ModelType }).currentValue
$Limit = ($ModelInfo | Where-Object { $_.name.value -eq $ModelType }).limit

$CurrentValue = [int]($CurrentValue -replace '\.0+$', '')
$Limit = [int]($Limit -replace '\.0+$', '')
$Available = $Limit - $CurrentValue

$script:AllResults += [PSCustomObject]@{
Region = $Region
Model = $ModelType
Limit = $Limit
Used = $CurrentValue
Available = $Available
}
}

foreach ($region in $PreferredRegions) {
Check-Quota -Region $region
}

# Display Results Table
Write-Host "\n-------------------------------------------------------------------------------------------------------------"
Write-Host "| No. | Region | Model Name | Limit | Used | Available |"
Write-Host "-------------------------------------------------------------------------------------------------------------"

$count = 1
foreach ($entry in $AllResults) {
$index = $PreferredRegions.IndexOf($entry.Region) + 1
$modelShort = $entry.Model.Substring($entry.Model.LastIndexOf(".") + 1)
Write-Host ("| {0,-4} | {1,-16} | {2,-35} | {3,-7} | {4,-7} | {5,-9} |" -f $index, $entry.Region, $entry.Model, $entry.Limit, $entry.Used, $entry.Available)
$count++
}
Write-Host "-------------------------------------------------------------------------------------------------------------"

$EligibleRegion = $AllResults | Where-Object { $_.Region -eq $Location -and $_.Available -ge $Capacity }
if ($EligibleRegion) {
Write-Host "\n✅ Sufficient quota found in original region '$Location'."
exit 0
}

$FallbackRegions = $AllResults | Where-Object { $_.Region -ne $Location -and $_.Available -ge $Capacity }

if ($FallbackRegions.Count -gt 0) {
Write-Host "`n❌ Deployment cannot proceed because the original region '$Location' lacks sufficient quota."
Write-Host "➡️ You can retry using one of the following regions with sufficient quota:`n"

foreach ($region in $FallbackRegions) {
Write-Host " • $($region.Region) (Available: $($region.Available))"
}

Write-Host "`n🔧 To proceed, run:"
Write-Host " azd env set AZURE_ENV_OPENAI_LOCATION '<region>'"
Write-Host "📌 To confirm it's set correctly, run:"
Write-Host " azd env get-value AZURE_ENV_OPENAI_LOCATION"
Write-Host "▶️ Once confirmed, re-run azd up to deploy the model in the new region."
exit 2
}

Write-Error "❌ ERROR: No available quota found in any region."
exit 1
Loading
Loading