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
5 changes: 5 additions & 0 deletions .changeset/apim-autoscaling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gradientedge/cdk-utils-azure': patch
---

Add configurable Azure API Management autoscaling for v2 tiers with dynamic resource targeting.
43 changes: 43 additions & 0 deletions packages/azure/src/construct/rest-api/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
import { getComponentOutput, GetComponentResult } from '@pulumi/azure-native/applicationinsights/index.js'
import { PrincipalType } from '@pulumi/azure-native/authorization/index.js'
import { getVaultOutput } from '@pulumi/azure-native/keyvault/index.js'
import { AutoscaleSetting } from '@pulumi/azure-native/monitor/index.js'
import * as pulumi from '@pulumi/pulumi'
import { Output } from '@pulumi/pulumi'
import _ from 'lodash'

import { CommonAzureConstruct } from '../../common/index.js'
import { RoleDefinitionId } from '../../services/index.js'
Expand Down Expand Up @@ -36,6 +38,8 @@ export class AzureRestApi extends CommonAzureConstruct {
api: AzureApi = {} as AzureApi
/** The resolved Application Insights component for telemetry */
applicationInsights: Output<GetComponentResult>
/** Azure Monitor autoscale setting for the API Management service */
apiManagementAutoscaleSetting?: AutoscaleSetting

/**
* @summary Create a new AzureRestApi
Expand All @@ -57,6 +61,7 @@ export class AzureRestApi extends CommonAzureConstruct {
this.resolveCommonLogAnalyticsWorkspace()
this.resolveApplicationInsights()
this.createApiManagement()
this.createApiManagementAutoscale()
this.createNamespaceSecretRole()
this.createNamespaceSecret()
this.createSubscriptionKeySecret()
Expand Down Expand Up @@ -168,6 +173,44 @@ export class AzureRestApi extends CommonAzureConstruct {
})
}

/** Configure Azure Monitor custom autoscale for v2 API Management tiers. */
protected createApiManagementAutoscale() {
const apiManagementService = this.api.apim
const autoscaleProps = this.props.apiManagementAutoscaling

if (this.props.apiManagement.useExistingApiManagement || !apiManagementService || !autoscaleProps) return

const configuredProfiles = autoscaleProps.profiles as unknown as
Array<{ rules?: Array<Record<string, unknown>> }> | undefined
const profiles = configuredProfiles?.map(profile => ({
...profile,
rules: profile.rules?.map(rule =>
_.merge({}, rule, {
metricTrigger: {
metricResourceUri: apiManagementService.id,
metricResourceLocation: this.resourceGroup.location,
},
})
),
}))

this.apiManagementAutoscaleSetting = this.monitorManager.createMonitorAutoscaleSettings(
`${this.id}-apim-autoscale`,
this,
{
..._.merge({}, autoscaleProps, { profiles }),
name: `${this.id}-apim-autoscale`,
enabled: autoscaleProps.enabled ?? true,
location: this.resourceGroup.location,
resourceGroupName: this.resourceGroup.name,
targetResourceUri: apiManagementService.id,
},
{ dependsOn: [apiManagementService] }
)

return this.apiManagementAutoscaleSetting
}

/**
* @summary Method to create the Key Vault role assignment for the API Management identity
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/azure/src/construct/rest-api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CommonAzureStackProps,
MonitorDiagnosticSettingProps,
CertificateProps,
MonitorAutoscaleSettingProps,
} from '../../index.js'

/**
Expand Down Expand Up @@ -38,6 +39,8 @@ export interface AzureRestApiProps extends CommonAzureStackProps {
apiManagementDiagnosticSettings: MonitorDiagnosticSettingProps
/** API Management certificate properties */
apiManagementCertificate: CertificateProps
/** API Management autoscaling settings for v2 tiers */
apiManagementAutoscaling?: MonitorAutoscaleSettingProps
}

/**
Expand Down
32 changes: 32 additions & 0 deletions packages/azure/test/common/config/rest-api-new.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,38 @@
"capacity": 0
}
},
"apiManagementAutoscaling": {
"profiles": [
{
"name": "default",
"capacity": {
"minimum": "1",
"default": "2",
"maximum": "10"
},
"rules": [
{
"metricTrigger": {
"metricName": "CpuPercent_Gateway",
"metricNamespace": "Microsoft.ApiManagement/service",
"operator": "GreaterThan",
"statistic": "Average",
"threshold": 60,
"timeAggregation": "Average",
"timeGrain": "PT1M",
"timeWindow": "PT30M"
},
"scaleAction": {
"cooldown": "PT60M",
"direction": "Increase",
"type": "ChangeCount",
"value": "1"
}
}
]
}
]
},
"apiManagementDiagnostic": {
"diagnosticId": "applicationinsights",
"apiId": "/subscriptions/test-sub/resourceGroups/test-rg-dev/providers/Microsoft.ApiManagement/service/test-api-management-dev/apis/test-api"
Expand Down
31 changes: 31 additions & 0 deletions packages/azure/test/constructs/rest-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class TestRestApiNewApiConstruct extends AzureRestApi {
this.resolveCommonLogAnalyticsWorkspace()
this.resolveApplicationInsights()
this.createApiManagement()
this.createApiManagementAutoscale()
this.createNamespaceSecretRole()
this.createNamespaceSecret()
this.createSubscriptionKeySecret()
Expand Down Expand Up @@ -159,6 +160,7 @@ class TestRestApiNewApiWithCertConstruct extends AzureRestApi {
this.resolveCommonLogAnalyticsWorkspace()
this.resolveApplicationInsights()
this.createApiManagement()
this.createApiManagementAutoscale()
this.createNamespaceSecretRole()
this.createNamespaceSecret()
this.createSubscriptionKeySecret()
Expand Down Expand Up @@ -367,6 +369,35 @@ describe('TestAzureRestApiNewApiConstruct', () => {
})
})

describe('TestAzureRestApiNewApiConstruct', () => {
test('provisions enabled APIM autoscale with dynamic target fields', async () => {
await outputToPromise(
pulumi
.all([
stackNewApi.construct.apiManagementAutoscaleSetting?.enabled,
stackNewApi.construct.apiManagementAutoscaleSetting?.profiles,
stackNewApi.construct.api.apim.id,
stackNewApi.construct.resourceGroup.location,
])
.apply(([enabled, profiles, apiManagementId, location]) => {
expect(enabled).toBe(true)
expect(profiles).toEqual([
expect.objectContaining({
rules: [
expect.objectContaining({
metricTrigger: expect.objectContaining({
metricResourceUri: apiManagementId,
metricResourceLocation: location,
}),
}),
],
}),
])
})
)
})
})

describe('TestAzureRestApiNewApiConstruct', () => {
test('creates namespace secret role as expected', async () => {
expect(stackNewApi.construct.api.namedValueRoleAssignment).toBeDefined()
Expand Down
Loading