Skip to content

Commit

Permalink
feat(amazon): support ALBRequestCountPerTarget scaling policies
Browse files Browse the repository at this point in the history
The UI for TargetTracking scaling policies didn't support ALBRequestCountPerTarget. This was the only missing metric documented here: https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-target-tracking.html

This requires setting the resourceLabel parameter which is a combination of part of both the ALB and target group ARNs. This metric also only works with the Sum statistic. I found that the chart component hard coded average so that had to be fixed up as well.
  • Loading branch information
chris-h-phillips committed Jan 1, 2025
1 parent 7486d2e commit 9a65f61
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 10 deletions.
7 changes: 6 additions & 1 deletion packages/amazon/src/domain/ITargetTrackingPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export interface ICustomizedMetricSpecification {

export interface IPredefinedMetricSpecification {
predefinedMetricType: PredefinedMetricType;
resourceLabel?: string;
}

export type PredefinedMetricType = 'ASGAverageCPUUtilization' | 'ASGAverageNetworkIn' | 'ASGAverageNetworkOut';
export type PredefinedMetricType =
| 'ASGAverageCPUUtilization'
| 'ASGAverageNetworkIn'
| 'ASGAverageNetworkOut'
| 'ALBRequestCountPerTarget';
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { get } from 'lodash';
import * as React from 'react';

import type { ICloudMetricStatistics } from '@spinnaker/core';
Expand Down Expand Up @@ -36,7 +37,7 @@ export function MetricAlarmChartImpl(props: IMetricAlarmChartProps) {
return result;
},
{ datapoints: [], unit: '' },
[namespace, statistic, period, type, account, region, metricName],
[namespace, statistic, period, type, account, region, metricName, alarm.dimensions],
);

if (status === 'PENDING') {
Expand All @@ -60,13 +61,15 @@ export function MetricAlarmChartImpl(props: IMetricAlarmChartProps) {

const now = new Date();
const oneDayAgo = new Date(Date.now() - 1000 * 60 * 60 * 24);

const line: IDateLine = {
label: metricName,
fill: 'stack',
borderColor: 'green',
borderWidth: 2,
data: result.datapoints.map((dp) => ({ x: new Date(dp.timestamp), y: dp.average })),
data: result.datapoints.map((dp) => ({
x: new Date(dp.timestamp),
y: get(dp, [alarm.statistic.toLowerCase()], undefined),
})),
};

const setline: IDateLine = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ import { cloneDeep, set } from 'lodash';
import * as React from 'react';

import { NumberInput, ReactSelectInput } from '@spinnaker/core';
import type { Application, ILoadBalancer } from '@spinnaker/core';

import type { ITargetTrackingPolicyCommand } from '../ScalingPolicyWriter';
import { TargetTrackingChart } from './TargetTrackingChart';
import type { IAmazonServerGroup, ICustomizedMetricSpecification, IScalingPolicyAlarmView } from '../../../../domain';
import type {
IAmazonApplicationLoadBalancer,
IAmazonServerGroup,
ICustomizedMetricSpecification,
IScalingPolicyAlarmView,
ITargetGroup,
} from '../../../../domain';
import { MetricSelector } from '../upsert/alarm/MetricSelector';

import './TargetMetricFields.less';
Expand All @@ -16,21 +23,36 @@ export interface ITargetMetricFieldsProps {
cloudwatch?: boolean;
command: ITargetTrackingPolicyCommand;
isCustomMetric: boolean;
app: Application;
serverGroup: IAmazonServerGroup;
toggleMetricType?: (type: MetricType) => void;
updateCommand: (command: ITargetTrackingPolicyCommand) => void;
}

interface IalbArn {
loadBalancerArn: string;
}

interface ItargetGroupArn {
targetGroupArn: string;
}

export const TargetMetricFields = ({
allowDualMode,
cloudwatch,
command,
isCustomMetric,
app,
serverGroup,
toggleMetricType,
updateCommand,
}: ITargetMetricFieldsProps) => {
const predefinedMetrics = ['ASGAverageCPUUtilization', 'ASGAverageNetworkOut', 'ASGAverageNetworkIn'];
const predefinedMetrics = [
'ASGAverageCPUUtilization',
'ASGAverageNetworkOut',
'ASGAverageNetworkIn',
'ALBRequestCountPerTarget',
];
const statistics = ['Average', 'Maximum', 'Minimum', 'SampleCount', 'Sum'];
const [unit, setUnit] = React.useState<string>(null);

Expand Down Expand Up @@ -65,6 +87,24 @@ export const TargetMetricFields = ({
toggleMetricType(isCustomMetric ? 'predefined' : 'custom');
};

const targetGroupOptions = () => {
const loadBalancers = app.loadBalancers.data as ILoadBalancer[];
const albs = loadBalancers.filter(
(lb) => lb.account === serverGroup.account && lb.region === serverGroup.region,
) as Array<IAmazonApplicationLoadBalancer & IalbArn>;
const targetGroups = albs.flatMap((alb) =>
alb.targetGroups
.filter((tg) => serverGroup.targetGroups.some((serverGroupTg) => serverGroupTg === tg.name))
.map((tg) => ({ ...tg, loadBalancerArn: alb.loadBalancerArn })),
) as Array<ITargetGroup & IalbArn & ItargetGroupArn>;
return targetGroups.map((tg) => ({
label: tg.name,
value: `${tg.loadBalancerArn.substring(tg.loadBalancerArn.indexOf('app'))}/${tg.targetGroupArn.substring(
tg.targetGroupArn.indexOf('targetgroup'),
)}`,
}));
};

return (
<div className="TargetMetricFields sp-margin-l-xaxis">
<p>
Expand Down Expand Up @@ -94,6 +134,21 @@ export const TargetMetricFields = ({
inputClassName="metric-select-input"
/>
)}
{!isCustomMetric &&
command.targetTrackingConfiguration.predefinedMetricSpecification?.predefinedMetricType ===
'ALBRequestCountPerTarget' && (
<ReactSelectInput
value={command.targetTrackingConfiguration.predefinedMetricSpecification?.resourceLabel}
options={targetGroupOptions()}
onChange={(e) =>
setCommandField(
'targetTrackingConfiguration.predefinedMetricSpecification.resourceLabel',
e.target.value,
)
}
inputClassName="metric-select-input"
/>
)}
{isCustomMetric && (
<MetricSelector
alarm={command.targetTrackingConfiguration.customizedMetricSpecification as IScalingPolicyAlarmView}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const predefinedMetricTypeMapping: Dictionary<string> = {
ASGAverageCPUUtilization: 'CPUUtilization',
ASGAverageNetworkIn: 'NetworkIn',
ASGAverageNetworkOut: 'NetworkOut',
ALBRequestCountPerTarget: 'RequestCountPerTarget',
};

export const TargetTrackingChart = ({ config, serverGroup, updateUnit }: ITargetTrackingChartProps) => {
Expand All @@ -40,12 +41,11 @@ export const TargetTrackingChart = ({ config, serverGroup, updateUnit }: ITarget

const synchronizeAlarm = () => {
const customMetric = config?.customizedMetricSpecification;
const predefMetric = config?.predefinedMetricSpecification;
const updatedAlarm = {
...alarm,
dimensions: customMetric?.dimensions || [{ name: 'AutoScalingGroupName', value: serverGroup.name }],
metricName:
customMetric?.metricName ||
predefinedMetricTypeMapping[config?.predefinedMetricSpecification?.predefinedMetricType],
metricName: customMetric?.metricName || predefinedMetricTypeMapping[predefMetric?.predefinedMetricType],
namespace: customMetric?.namespace || 'AWS/EC2',
threshold: config?.targetValue,
};
Expand All @@ -54,6 +54,20 @@ export const TargetTrackingChart = ({ config, serverGroup, updateUnit }: ITarget
updatedAlarm.statistic = customMetric?.statistic;
}

if (predefMetric && predefMetric.predefinedMetricType === 'ALBRequestCountPerTarget') {
updatedAlarm.statistic = 'Sum';
updatedAlarm.namespace = 'AWS/ApplicationELB';
if (predefMetric?.resourceLabel) {
const parts = predefMetric?.resourceLabel.split('/');
const loadBalancer = parts.slice(0, 3).join('/');
const targetGroup = parts.slice(3).join('/');
updatedAlarm.dimensions = [
{ name: 'LoadBalancer', value: loadBalancer },
{ name: 'TargetGroup', value: targetGroup },
];
}
}

setAlarm(updatedAlarm);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const UpsertTargetTrackingModal = ({
cloudwatch={false}
command={command as ITargetTrackingPolicyCommand}
isCustomMetric={isCustom}
app={app}
serverGroup={serverGroup}
toggleMetricType={(t) => setIsCustom(t === 'custom')}
updateCommand={setCommand}
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/domain/ICloudMetric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export interface IMetricAlarmDimension {

interface IDataPoint {
timestamp: number;
average: number;
average?: number;
sum?: number;
minimum?: number;
maximum?: number;
sampleCount?: number;
}

export interface ICloudMetricStatistics {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const UpsertTargetTrackingModal = ({
cloudwatch={true}
command={command as ITargetTrackingPolicyCommand}
isCustomMetric={isCustom}
app={app}
serverGroup={(serverGroup as unknown) as IAmazonServerGroup}
toggleMetricType={(t) => setIsCustom(t === 'custom')}
updateCommand={setCommand}
Expand Down

0 comments on commit 9a65f61

Please sign in to comment.