Skip to content

feat: Added Quota Auto Validation Scripts #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
16 changes: 16 additions & 0 deletions azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,19 @@ deployment:
AzureAiServiceLocation: ${{ parameters.AzureAiServiceLocation }}
Prefix: ${{ parameters.Prefix }}
baseUrl: ${{ parameters.baseUrl }}
hooks:
preprovision:
posix:
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 --subscription "$AZURE_SUBSCRIPTION_ID" --location "${AZURE_AISERVICE_LOCATION:-japaneast}" --models-parameter "aiModelDeployments"
interactive: false
continueOnError: false

windows:
shell: pwsh
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: false
continueOnError: false
35 changes: 12 additions & 23 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,19 @@ param Prefix string
var abbrs = loadJsonContent('./abbreviations.json')
var safePrefix = length(Prefix) > 20 ? substring(Prefix, 0, 20) : Prefix

@description('Required. Location for all Resources except AI Foundry.')
param solutionLocation string = resourceGroup().location

@allowed([
'australiaeast'
'brazilsouth'
'canadacentral'
'canadaeast'
'eastus'
'eastus2'
'francecentral'
'germanywestcentral'
'japaneast'
'koreacentral'
'northcentralus'
'norwayeast'
'polandcentral'
'southafricanorth'
'southcentralus'
'southindia'
'swedencentral'
'switzerlandnorth'
'uaenorth'
'uksouth'
'westeurope'
'westus'
'westus3'
])
Expand Down Expand Up @@ -57,8 +48,6 @@ param gptModelVersion string = '2024-08-06'
var uniqueId = toLower(uniqueString(subscription().id, safePrefix, resourceGroup().location))
var UniquePrefix = 'cm${padLeft(take(uniqueId, 12), 12, '0')}'
var ResourcePrefix = take('cm${safePrefix}${UniquePrefix}', 15)
var location = resourceGroup().location
var dblocation = resourceGroup().location
var cosmosdbDatabase = 'cmsadb'
var cosmosdbBatchContainer = 'cmsabatch'
var cosmosdbFileContainer = 'cmsafile'
Expand All @@ -85,7 +74,7 @@ var aiModelDeployments = [

resource azureAiServices 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' = {
name: azureAiServicesName
location: location
location: AzureAiServiceLocation
sku: {
name: 'S0'
}
Expand Down Expand Up @@ -121,7 +110,7 @@ module managedIdentityModule 'deploy_managed_identity.bicep' = {
params: {
miName:'${abbrs.security.managedIdentity}${ResourcePrefix}'
solutionName: ResourcePrefix
solutionLocation: location
solutionLocation: solutionLocation
}
scope: resourceGroup(resourceGroup().name)
}
Expand All @@ -133,7 +122,7 @@ module kvault 'deploy_keyvault.bicep' = {
params: {
keyvaultName: '${abbrs.security.keyVault}${ResourcePrefix}'
solutionName: ResourcePrefix
solutionLocation: location
solutionLocation: solutionLocation
managedIdentityObjectId:managedIdentityModule.outputs.managedIdentityOutput.objectId
}
scope: resourceGroup(resourceGroup().name)
Expand Down Expand Up @@ -162,7 +151,7 @@ module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.9.1
params: {
logAnalyticsWorkspaceResourceId: azureAifoundry.outputs.logAnalyticsId
name: toLower('${ResourcePrefix}manenv')
location: location
location: solutionLocation
zoneRedundant: false
managedIdentities: managedIdentityModule
}
Expand All @@ -175,7 +164,7 @@ module databaseAccount 'br/public:avm/res/document-db/database-account:0.9.0' =
name: toLower('${abbrs.databases.cosmosDBDatabase}${ResourcePrefix}databaseAccount')
// Non-required parameters
enableAnalyticalStorage: true
location: dblocation
location: solutionLocation
managedIdentities: {
systemAssigned: true
userAssignedResourceIds: [
Expand All @@ -193,7 +182,7 @@ module databaseAccount 'br/public:avm/res/document-db/database-account:0.9.0' =
{
failoverPriority: 0
isZoneRedundant: false
locationName: dblocation
locationName: solutionLocation
}
]
sqlDatabases: [
Expand Down Expand Up @@ -268,14 +257,14 @@ module containerAppFrontend 'br/public:avm/res/app/container-app:0.13.0' = {
environmentResourceId: containerAppsEnvironment.outputs.resourceId
name: toLower('${abbrs.containers.containerApp}${ResourcePrefix}Frontend')
// Non-required parameters
location: location
location: solutionLocation
}
}


resource containerAppBackend 'Microsoft.App/containerApps@2023-05-01' = {
name: toLower('${abbrs.containers.containerApp}${ResourcePrefix}Backend')
location: location
location: solutionLocation
identity: {
type: 'SystemAssigned'
}
Expand Down Expand Up @@ -393,7 +382,7 @@ resource containerAppBackend 'Microsoft.App/containerApps@2023-05-01' = {
}
resource storageContianerApp 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageContainerName
location: location
location: solutionLocation
sku: {
name: storageSkuName
}
Expand Down
3 changes: 2 additions & 1 deletion infra/main.bicepparam
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using './main.bicep'

param Prefix = readEnvironmentVariable('AZURE_ENV_NAME','azdtemp')
param AzureAiServiceLocation = readEnvironmentVariable('AZURE_LOCATION','japaneast')
param solutionLocation = readEnvironmentVariable('AZURE_LOCATION', 'eastus2')
param AzureAiServiceLocation = readEnvironmentVariable('AZURE_AISERVICE_LOCATION','japaneast')
param capacity = int(readEnvironmentVariable('AZURE_ENV_MODEL_CAPACITY', '200'))
param deploymentType = readEnvironmentVariable('AZURE_ENV_MODEL_DEPLOYMENT_TYPE', 'GlobalStandard')
param llmModel = readEnvironmentVariable('AZURE_ENV_MODEL_NAME', 'gpt-4o')
Expand Down
18 changes: 17 additions & 1 deletion infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@
},
"principalId": {
"value": "${AZURE_PRINCIPAL_ID}"
}
},
"aiModelDeployments": {
"value": [
{
"name": "gpt-4o",
"model": {
"name": "gpt-4o",
"version": "2024-08-06",
"format": "OpenAI"
},
"sku": {
"name": "GlobalStandard",
"capacity": 200
}
}
]
}
}
}
2 changes: 1 addition & 1 deletion scripts/quota_check_params.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,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,eastus,eastus2,francecentral,japaneast,norwayeast,southindia,swedencentral,uksouth,westus,westus3"
IFS=',' read -r -a DEFAULT_REGION_ARRAY <<< "$DEFAULT_REGIONS"

# Read parameters (if any)
Expand Down
73 changes: 73 additions & 0 deletions scripts/validate_model_deployment_quota.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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 = if ($env:AZURE_ENV_MODEL_NAME) { $env:AZURE_ENV_MODEL_NAME } else { $deployment.name }
$model = if ($env:AZURE_ENV_MODEL_NAME) { $env:AZURE_ENV_MODEL_NAME } else { $deployment.model.name }
$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 "`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) {
# 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
}
88 changes: 88 additions & 0 deletions 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=${AZURE_ENV_MODEL_NAME:-$(echo "$deployment" | jq -r '.name')}
model=${AZURE_ENV_MODEL_NAME:-$(echo "$deployment" | jq -r '.model.name')}
type=${AZURE_ENV_MODEL_DEPLOYMENT_TYPE:-$(echo "$deployment" | jq -r '.sku.name')}
capacity=${AZURE_ENV_MODEL_CAPACITY:-$(echo "$deployment" | jq -r '.sku.capacity')}

echo "🔍 Validating model deployment: $name ..."
./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
Loading