Skip to content

Commit

Permalink
feat(cloudwatch): CompositeAlarm (#8498)
Browse files Browse the repository at this point in the history
* fix #8462
----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
SeekerWing committed Jun 22, 2020
1 parent 5053eb7 commit 1e6d293
Show file tree
Hide file tree
Showing 12 changed files with 627 additions and 69 deletions.
23 changes: 23 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,29 @@ const alarm = new cloudwatch.Alarm(stack, 'Alarm', { /* ... */ });
alarm.addAlarmAction(new cw_actions.SnsAction(topic));
```

### Composite Alarms

[Composite Alarms](https://aws.amazon.com/about-aws/whats-new/2020/03/amazon-cloudwatch-now-allows-you-to-combine-multiple-alarms/)
can be created from existing Alarm resources.

```ts
const alarmRule = AlarmRule.anyOf(
AlarmRule.allOf(
AlarmRule.anyOf(
alarm1,
AlarmRule.fromAlarm(alarm2, AlarmState.OK),
alarm3,
),
AlarmRule.not(AlarmRule.fromAlarm(alarm4, AlarmState.INSUFFICIENT_DATA)),
),
AlarmRule.fromBoolean(false),
);

new CompositeAlarm(this, 'MyAwesomeCompositeAlarm', {
alarmRule,
});
```

### A note on units

In CloudWatch, Metrics datums are emitted with units, such as `seconds` or
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-cloudwatch/lib/alarm-action.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Construct } from '@aws-cdk/core';
import { IAlarm } from './alarm';
import { IAlarm } from './alarm-base';

/**
* Interface for objects that can be the targets of CloudWatch alarm actions
Expand Down
96 changes: 96 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/alarm-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { IResource, Resource } from '@aws-cdk/core';
import { IAlarmAction } from './alarm-action';

/**
* Interface for Alarm Rule.
*/
export interface IAlarmRule {

/**
* serialized representation of Alarm Rule to be used when building the Composite Alarm resource.
*/
renderAlarmRule(): string;

}

/**
* Represents a CloudWatch Alarm
*/
export interface IAlarm extends IAlarmRule, IResource {
/**
* Alarm ARN (i.e. arn:aws:cloudwatch:<region>:<account-id>:alarm:Foo)
*
* @attribute
*/
readonly alarmArn: string;

/**
* Name of the alarm
*
* @attribute
*/
readonly alarmName: string;
}

/**
* The base class for Alarm and CompositeAlarm resources.
*/
export abstract class AlarmBase extends Resource implements IAlarm {

/**
* @attribute
*/
public abstract readonly alarmArn: string;
public abstract readonly alarmName: string;

protected alarmActionArns?: string[];
protected insufficientDataActionArns?: string[];
protected okActionArns?: string[];

/**
* AlarmRule indicating ALARM state for Alarm.
*/
public renderAlarmRule(): string {
return `ALARM(${this.alarmArn})`;
}

/**
* Trigger this action if the alarm fires
*
* Typically the ARN of an SNS topic or ARN of an AutoScaling policy.
*/
public addAlarmAction(...actions: IAlarmAction[]) {
if (this.alarmActionArns === undefined) {
this.alarmActionArns = [];
}

this.alarmActionArns.push(...actions.map(a => a.bind(this, this).alarmActionArn));
}

/**
* Trigger this action if there is insufficient data to evaluate the alarm
*
* Typically the ARN of an SNS topic or ARN of an AutoScaling policy.
*/
public addInsufficientDataAction(...actions: IAlarmAction[]) {
if (this.insufficientDataActionArns === undefined) {
this.insufficientDataActionArns = [];
}

this.insufficientDataActionArns.push(...actions.map(a => a.bind(this, this).alarmActionArn));
}

/**
* Trigger this action if the alarm returns from breaching state into ok state
*
* Typically the ARN of an SNS topic or ARN of an AutoScaling policy.
*/
public addOkAction(...actions: IAlarmAction[]) {
if (this.okActionArns === undefined) {
this.okActionArns = [];
}

this.okActionArns.push(...actions.map(a => a.bind(this, this).alarmActionArn));
}

}
127 changes: 127 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/alarm-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { IAlarm, IAlarmRule } from './alarm-base';

/**
* Enumeration indicates state of Alarm used in building Alarm Rule.
*/
export enum AlarmState {

/**
* State indicates resource is in ALARM
*/
ALARM = 'ALARM',

/**
* State indicates resource is not in ALARM
*/
OK = 'OK',

/**
* State indicates there is not enough data to determine is resource is in ALARM
*/
INSUFFICIENT_DATA = 'INSUFFICIENT_DATA',

}

/**
* Enumeration of supported Composite Alarms operators.
*/
enum Operator {

AND = 'AND',
OR = 'OR',
NOT = 'NOT',

}

/**
* Class with static functions to build AlarmRule for Composite Alarms.
*/
export class AlarmRule {

/**
* function to join all provided AlarmRules with AND operator.
*
* @param operands IAlarmRules to be joined with AND operator.
*/
public static allOf(...operands: IAlarmRule[]): IAlarmRule {
return this.concat(Operator.AND, ...operands);
}

/**
* function to join all provided AlarmRules with OR operator.
*
* @param operands IAlarmRules to be joined with OR operator.
*/
public static anyOf(...operands: IAlarmRule[]): IAlarmRule {
return this.concat(Operator.OR, ...operands);
}

/**
* function to wrap provided AlarmRule in NOT operator.
*
* @param operand IAlarmRule to be wrapped in NOT operator.
*/
public static not(operand: IAlarmRule): IAlarmRule {
// tslint:disable-next-line:new-parens
return new class implements IAlarmRule {
public renderAlarmRule(): string {
return `(NOT (${operand.renderAlarmRule()}))`;
}
};
}

/**
* function to build TRUE/FALSE intent for Rule Expression.
*
* @param value boolean value to be used in rule expression.
*/
public static fromBoolean(value: boolean): IAlarmRule {
// tslint:disable-next-line:new-parens
return new class implements IAlarmRule {
public renderAlarmRule(): string {
return `${String(value).toUpperCase()}`;
}
};
}

/**
* function to build Rule Expression for given IAlarm and AlarmState.
*
* @param alarm IAlarm to be used in Rule Expression.
* @param alarmState AlarmState to be used in Rule Expression.
*/
public static fromAlarm(alarm: IAlarm, alarmState: AlarmState): IAlarmRule {
// tslint:disable-next-line:new-parens
return new class implements IAlarmRule {
public renderAlarmRule(): string {
return `${alarmState}(${alarm.alarmArn})`;
}
};
}

/**
* function to build Rule Expression for given Alarm Rule string.
*
* @param alarmRule string to be used in Rule Expression.
*/
public static fromString(alarmRule: string): IAlarmRule {
// tslint:disable-next-line:new-parens
return new class implements IAlarmRule {
public renderAlarmRule(): string {
return alarmRule;
}
};
}

private static concat(operator: Operator, ...operands: IAlarmRule[]): IAlarmRule {
// tslint:disable-next-line:new-parens
return new class implements IAlarmRule {
public renderAlarmRule(): string {
const expression = operands
.map(operand => `${operand.renderAlarmRule()}`)
.join(` ${operator} `);
return `(${expression})`;
}
};
}
}
70 changes: 4 additions & 66 deletions packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Construct, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core';
import { IAlarmAction } from './alarm-action';
import { Construct, Lazy, Stack, Token } from '@aws-cdk/core';
import { AlarmBase, IAlarm } from './alarm-base';
import { CfnAlarm, CfnAlarmProps } from './cloudwatch.generated';
import { HorizontalAnnotation } from './graph';
import { CreateAlarmOptions } from './metric';
Expand All @@ -9,25 +9,6 @@ import { dropUndefined } from './private/object';
import { MetricSet } from './private/rendering';
import { parseStatistic } from './private/statistic';

/**
* Represents a CloudWatch Alarm
*/
export interface IAlarm extends IResource {
/**
* Alarm ARN (i.e. arn:aws:cloudwatch:<region>:<account-id>:alarm:Foo)
*
* @attribute
*/
readonly alarmArn: string;

/**
* Name of the alarm
*
* @attribute
*/
readonly alarmName: string;
}

/**
* Properties for Alarms
*/
Expand Down Expand Up @@ -107,7 +88,7 @@ export enum TreatMissingData {
/**
* An alarm on a CloudWatch metric
*/
export class Alarm extends Resource implements IAlarm {
export class Alarm extends AlarmBase {

/**
* Import an existing CloudWatch alarm provided an ARN
Expand All @@ -117,7 +98,7 @@ export class Alarm extends Resource implements IAlarm {
* @param alarmArn Alarm ARN (i.e. arn:aws:cloudwatch:<region>:<account-id>:alarm:Foo)
*/
public static fromAlarmArn(scope: Construct, id: string, alarmArn: string): IAlarm {
class Import extends Resource implements IAlarm {
class Import extends AlarmBase implements IAlarm {
public readonly alarmArn = alarmArn;
public readonly alarmName = Stack.of(scope).parseArn(alarmArn, ':').resourceName!;
}
Expand All @@ -143,10 +124,6 @@ export class Alarm extends Resource implements IAlarm {
*/
public readonly metric: IMetric;

private alarmActionArns?: string[];
private insufficientDataActionArns?: string[];
private okActionArns?: string[];

/**
* This metric as an annotation
*/
Expand Down Expand Up @@ -214,45 +191,6 @@ export class Alarm extends Resource implements IAlarm {
};
}

/**
* Trigger this action if the alarm fires
*
* Typically the ARN of an SNS topic or ARN of an AutoScaling policy.
*/
public addAlarmAction(...actions: IAlarmAction[]) {
if (this.alarmActionArns === undefined) {
this.alarmActionArns = [];
}

this.alarmActionArns.push(...actions.map(a => a.bind(this, this).alarmActionArn));
}

/**
* Trigger this action if there is insufficient data to evaluate the alarm
*
* Typically the ARN of an SNS topic or ARN of an AutoScaling policy.
*/
public addInsufficientDataAction(...actions: IAlarmAction[]) {
if (this.insufficientDataActionArns === undefined) {
this.insufficientDataActionArns = [];
}

this.insufficientDataActionArns.push(...actions.map(a => a.bind(this, this).alarmActionArn));
}

/**
* Trigger this action if the alarm returns from breaching state into ok state
*
* Typically the ARN of an SNS topic or ARN of an AutoScaling policy.
*/
public addOkAction(...actions: IAlarmAction[]) {
if (this.okActionArns === undefined) {
this.okActionArns = [];
}

this.okActionArns.push(...actions.map(a => a.bind(this, this).alarmActionArn));
}

/**
* Turn this alarm into a horizontal annotation
*
Expand Down

0 comments on commit 1e6d293

Please sign in to comment.