Skip to content
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

feat(cloudwatch): CompositeAlarm #8498

Merged
merged 6 commits into from
Jun 22, 2020
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
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
Loading