Skip to content

Commit

Permalink
feat(AlarmFactory): add suppressor alarm props to AddCompositeAlarmPr…
Browse files Browse the repository at this point in the history
…ops (#518)

### Description
Closes #516

CloudWatch `CompositeAlarm` now supports specifying a suppressor alarm
`actionsSuppressor` when initializing (see [CompositeAlarm API
reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-compositealarm.html)).
However, this additional parameter is not present within
`AddCompositeAlarmProps`, and thus, it is not possible to create a
`CompositeAlarm` using `AlarmFactory.addCompositeAlarm(...)` while
simultaneously specifying a suppressor alarm. This pull request adds
this functionality, while being backwards-compatible, since all new
props are optional.

### Testing
- `yarn build`
  - Added suite of unit tests to test suppressor alarm prop behavior.
  
---

_By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache-2.0 license_

Co-authored-by: Jonathan Luo <[email protected]>
  • Loading branch information
jonathan-luo and Jonathan Luo authored May 10, 2024
1 parent 619690d commit 736767e
Show file tree
Hide file tree
Showing 4 changed files with 429 additions and 0 deletions.
46 changes: 46 additions & 0 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions lib/common/alarm/AlarmFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ComparisonOperator,
CompositeAlarm,
HorizontalAnnotation,
IAlarm,
IAlarmRule,
IMetric,
MathExpression,
Expand Down Expand Up @@ -388,6 +389,29 @@ export interface AddCompositeAlarmProps {
* @default - OR
*/
readonly compositeOperator?: CompositeAlarmOperator;

/**
* Actions will be suppressed if the suppressor alarm is in the ALARM state.
*
* @default - no suppressor alarm
*/
readonly actionsSuppressor?: IAlarm;

/**
* The maximum time in seconds that the composite alarm waits after suppressor alarm goes out of the ALARM state.
* After this time, the composite alarm performs its actions.
*
* @default - 60 seconds
*/
readonly actionsSuppressorExtensionPeriod?: Duration;

/**
* The maximum duration that the composite alarm waits for the suppressor alarm to go into the ALARM state.
* After this time, the composite alarm performs its actions.
*
* @default - 60 seconds
*/
readonly actionsSuppressorWaitPeriod?: Duration;
}

/**
Expand Down Expand Up @@ -779,6 +803,9 @@ export class AlarmFactory {
alarmDescription,
alarmRule,
actionsEnabled,
actionsSuppressor: props?.actionsSuppressor,
actionsSuppressorExtensionPeriod: props?.actionsSuppressorExtensionPeriod,
actionsSuppressorWaitPeriod: props?.actionsSuppressorWaitPeriod,
});

action.addAlarmActions({
Expand Down
87 changes: 87 additions & 0 deletions test/common/alarm/AlarmFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,93 @@ test("addCompositeAlarm: snapshot for operator", () => {
expect(Template.fromStack(stack)).toMatchSnapshot();
});

test("addCompositeAlarm: snapshot for suppressor alarm props", () => {
const stack = new Stack();
const factory = new AlarmFactory(stack, {
globalMetricDefaults,
globalAlarmDefaults: globalAlarmDefaultsWithDisambiguator,
localAlarmNamePrefix: "prefix",
});
const alarm1 = factory.addAlarm(metric, {
alarmNameSuffix: "Alarm1",
alarmDescription: "Testing alarm 1",
threshold: 1,
comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
treatMissingData: TreatMissingData.MISSING,
});
const alarm2 = factory.addAlarm(metric, {
alarmNameSuffix: "Alarm2",
alarmDescription: "Testing alarm 2",
threshold: 2,
comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
treatMissingData: TreatMissingData.MISSING,
});
const suppressorAlarm = factory.addCompositeAlarm([alarm1, alarm2], {
disambiguator: "SuppressorAlarm",
alarmNameSuffix: "SuppressorAlarm",
});
factory.addCompositeAlarm([alarm1, alarm2], {
disambiguator: "SuppressedAlarmDefault",
alarmNameSuffix: "SuppressedAlarmDefault",
actionsSuppressor: suppressorAlarm,
});
factory.addCompositeAlarm([alarm1, alarm2], {
disambiguator: "SuppressedAlarmTestExtensionPeriod",
alarmNameSuffix: "SuppressedAlarmTestExtensionPeriod",
actionsSuppressor: suppressorAlarm,
actionsSuppressorExtensionPeriod: Duration.seconds(100),
});
factory.addCompositeAlarm([alarm1, alarm2], {
disambiguator: "SuppressedAlarmTestWaitPeriod",
alarmNameSuffix: "SuppressedAlarmTestWaitPeriod",
actionsSuppressor: suppressorAlarm,
actionsSuppressorWaitPeriod: Duration.seconds(100),
});
factory.addCompositeAlarm([alarm1, alarm2], {
disambiguator: "SuppressedAlarmTestBothExtensionAndWaitPeriod",
alarmNameSuffix: "SuppressedAlarmTestBothExtensionAndWaitPeriod",
actionsSuppressor: suppressorAlarm,
actionsSuppressorExtensionPeriod: Duration.seconds(100),
actionsSuppressorWaitPeriod: Duration.seconds(100),
});

expect(Template.fromStack(stack)).toMatchSnapshot();
});

test("addCompositeAlarm: actions suppressor extension period specified but no actions suppressor throws error", () => {
expect(() => {
factory.addCompositeAlarm([], {
disambiguator: "CompositeAlarmExtensionPeriodSpecifiedNoSuppressorAlarm",
alarmNameSuffix:
"CompositeAlarmExtensionPeriodSpecifiedNoSuppressorAlarm",
actionsSuppressorExtensionPeriod: Duration.seconds(100),
});
}).toThrow(Error);
});

test("addCompositeAlarm: actions suppressor wait period specified but no actions suppressor throws error", () => {
expect(() => {
factory.addCompositeAlarm([], {
disambiguator: "CompositeAlarmWaitPeriodSpecifiedNoSuppressorAlarm",
alarmNameSuffix: "CompositeAlarmWaitPeriodSpecifiedNoSuppressorAlarm",
actionsSuppressorWaitPeriod: Duration.seconds(100),
});
}).toThrow(Error);
});

test("addCompositeAlarm: actions suppressor both extension and wait periods specified but no actions suppressor throws error", () => {
expect(() => {
factory.addCompositeAlarm([], {
disambiguator:
"CompositeAlarmBothExtensionAndWaitPeriodSpecifiedNoSuppressorAlarm",
alarmNameSuffix:
"CompositeAlarmBothExtensionAndWaitPeriodSpecifiedNoSuppressorAlarm",
actionsSuppressorExtensionPeriod: Duration.seconds(100),
actionsSuppressorWaitPeriod: Duration.seconds(100),
});
}).toThrow(Error);
});

test("addAlarm: original actionOverride with a different action gets preserved", () => {
const originalActionOverride = new SnsAlarmActionStrategy({
onAlarmTopic: new Topic(stack, "Dummy1"),
Expand Down
Loading

0 comments on commit 736767e

Please sign in to comment.