Skip to content

Commit

Permalink
refactor(autoscaling): introduce Schedule classes for scaling (#2902)
Browse files Browse the repository at this point in the history
Like for CloudWatch Events, introduce a class for expressing scheduling expressions for both AutoScaling and AppScaling.

BREAKING CHANGES:

* **autoscaling**: `schedule` is now a `Schedule` object instead of a string.
* **application autoscaling**: `schedule` is now a `Schedule` object instead of a string.
  • Loading branch information
rix0rrr authored Jun 18, 2019
1 parent b3b3ba9 commit 3ae7511
Show file tree
Hide file tree
Showing 16 changed files with 277 additions and 84 deletions.
19 changes: 0 additions & 19 deletions packages/@aws-cdk/aws-applicationautoscaling/lib/cron.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-applicationautoscaling/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export * from './applicationautoscaling.generated';

export * from './base-scalable-attribute';
export * from './cron';
export * from './schedule';
export * from './scalable-target';
export * from './step-scaling-policy';
export * from './step-scaling-action';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import iam = require('@aws-cdk/aws-iam');
import { Construct, IResource, Resource } from '@aws-cdk/cdk';
import { CfnScalableTarget } from './applicationautoscaling.generated';
import { Schedule } from './schedule';
import { BasicStepScalingPolicyProps, StepScalingPolicy } from './step-scaling-policy';
import { BasicTargetTrackingScalingPolicyProps, TargetTrackingScalingPolicy } from './target-tracking-scaling-policy';

Expand Down Expand Up @@ -138,7 +139,7 @@ export class ScalableTarget extends Resource implements IScalableTarget {
}
this.actions.push({
scheduledActionName: id,
schedule: action.schedule,
schedule: action.schedule.expressionString,
startTime: action.startTime,
endTime: action.endTime,
scalableTargetAction: {
Expand Down Expand Up @@ -169,23 +170,8 @@ export class ScalableTarget extends Resource implements IScalableTarget {
export interface ScalingSchedule {
/**
* When to perform this action.
*
* Support formats:
* - at(yyyy-mm-ddThh:mm:ss)
* - rate(value unit)
* - cron(fields)
*
* "At" expressions are useful for one-time schedules. Specify the time in
* UTC.
*
* For "rate" expressions, value is a positive integer, and unit is minute,
* minutes, hour, hours, day, or days.
*
* For more information about cron expressions, see https://en.wikipedia.org/wiki/Cron.
*
* @example rate(12 hours)
*/
readonly schedule: string;
readonly schedule: Schedule;

/**
* When this scheduled action becomes active.
Expand Down
157 changes: 157 additions & 0 deletions packages/@aws-cdk/aws-applicationautoscaling/lib/schedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* Schedule for scheduled scaling actions
*/
export abstract class Schedule {
/**
* Construct a schedule from a literal schedule expression
*
* @param expression The expression to use. Must be in a format that Application AutoScaling will recognize
*/
public static expression(expression: string): Schedule {
return new LiteralSchedule(expression);
}

/**
* Construct a schedule from an interval and a time unit
*/
public static rate(interval: number, unit: TimeUnit): Schedule {
const unitStr = interval !== 1 ? `${unit}s` : unit;

return new LiteralSchedule(`rate(${interval} ${unitStr})`);
}

/**
* Construct a Schedule from a moment in time
*/
public static at(moment: Date): Schedule {
return new LiteralSchedule(`at(${formatISO(moment)})`);
}

/**
* Create a schedule from a set of cron fields
*/
public static cron(options: CronOptions): Schedule {
if (options.weekDay !== undefined && options.day !== undefined) {
throw new Error(`Cannot supply both 'day' and 'weekDay', use at most one`);
}

const minute = fallback(options.minute, '*');
const hour = fallback(options.hour, '*');
const month = fallback(options.month, '*');
const year = fallback(options.year, '*');

// Weekday defaults to '?' if not supplied. If it is supplied, day must become '?'
const day = fallback(options.day, options.weekDay !== undefined ? '?' : '*');
const weekDay = fallback(options.weekDay, '?');

return new LiteralSchedule(`cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`);
}

/**
* Retrieve the expression for this schedule
*/
public abstract readonly expressionString: string;

protected constructor() {
}
}

/**
* What unit to interpret the rate in
*/
export enum TimeUnit {
/**
* The rate is in minutes
*/
Minute = 'minute',

/**
* The rate is in hours
*/
Hour = 'hour',

/**
* The rate is in days
*/
Day = 'day'
}

/**
* Options to configure a cron expression
*
* All fields are strings so you can use complex expresions. Absence of
* a field implies '*' or '?', whichever one is appropriate.
*
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions
*/
export interface CronOptions {
/**
* The minute to run this rule at
*
* @default - Every minute
*/
readonly minute?: string;

/**
* The hour to run this rule at
*
* @default - Every hour
*/
readonly hour?: string;

/**
* The day of the month to run this rule at
*
* @default - Every day of the month
*/
readonly day?: string;

/**
* The month to run this rule at
*
* @default - Every month
*/
readonly month?: string;

/**
* The year to run this rule at
*
* @default - Every year
*/
readonly year?: string;

/**
* The day of the week to run this rule at
*
* @default - Any day of the week
*/
readonly weekDay?: string;
}

class LiteralSchedule extends Schedule {
constructor(public readonly expressionString: string) {
super();
}
}

function fallback<T>(x: T | undefined, def: T): T {
return x === undefined ? def : x;
}

function formatISO(date?: Date) {
if (!date) { return undefined; }

return date.getUTCFullYear() +
'-' + pad(date.getUTCMonth() + 1) +
'-' + pad(date.getUTCDate()) +
'T' + pad(date.getUTCHours()) +
':' + pad(date.getUTCMinutes()) +
':' + pad(date.getUTCSeconds());

function pad(num: number) {
if (num < 10) {
return '0' + num;
}
return num;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import appscaling = require('../lib');

export = {
'test utc cron, hour only'(test: Test) {
test.equals(appscaling.Cron.dailyUtc(18), 'cron(0 18 * * ?)');
test.equals(appscaling.Schedule.cron({ hour: '18', minute: '0' }).expressionString, 'cron(0 18 * * ? *)');
test.done();
},

'test utc cron, hour and minute'(test: Test) {
test.equals(appscaling.Cron.dailyUtc(18, 24), 'cron(24 18 * * ?)');
test.equals(appscaling.Schedule.cron({ hour: '18', minute: '24' }).expressionString, 'cron(24 18 * * ? *)');
test.done();
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export = {

// WHEN
target.scaleOnSchedule('ScaleUp', {
schedule: 'rate(1 second)',
schedule: appscaling.Schedule.rate(1, appscaling.TimeUnit.Minute),
maxCapacity: 50,
minCapacity: 1,
});
Expand All @@ -50,7 +50,7 @@ export = {
MaxCapacity: 50,
MinCapacity: 1
},
Schedule: "rate(1 second)",
Schedule: "rate(1 minute)",
ScheduledActionName: "ScaleUp"
}
]
Expand Down
19 changes: 0 additions & 19 deletions packages/@aws-cdk/aws-autoscaling/lib/cron.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-autoscaling/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './auto-scaling-group';
export * from './cron';
export * from './schedule';
export * from './lifecycle-hook';
export * from './lifecycle-hook-target';
export * from './scheduled-action';
Expand Down
94 changes: 94 additions & 0 deletions packages/@aws-cdk/aws-autoscaling/lib/schedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Schedule for scheduled scaling actions
*/
export abstract class Schedule {
/**
* Construct a schedule from a literal schedule expression
*
* @param expression The expression to use. Must be in a format that AutoScaling will recognize
* @see http://crontab.org/
*/
public static expression(expression: string): Schedule {
return new LiteralSchedule(expression);
}

/**
* Create a schedule from a set of cron fields
*/
public static cron(options: CronOptions): Schedule {
if (options.weekDay !== undefined && options.day !== undefined) {
throw new Error(`Cannot supply both 'day' and 'weekDay', use at most one`);
}

const minute = fallback(options.minute, '*');
const hour = fallback(options.hour, '*');
const month = fallback(options.month, '*');
const day = fallback(options.day, '*');
const weekDay = fallback(options.weekDay, '*');

return new LiteralSchedule(`${minute} ${hour} ${day} ${month} ${weekDay}`);
}

/**
* Retrieve the expression for this schedule
*/
public abstract readonly expressionString: string;

protected constructor() {
}
}

/**
* Options to configure a cron expression
*
* All fields are strings so you can use complex expresions. Absence of
* a field implies '*' or '?', whichever one is appropriate.
*
* @see http://crontab.org/
*/
export interface CronOptions {
/**
* The minute to run this rule at
*
* @default - Every minute
*/
readonly minute?: string;

/**
* The hour to run this rule at
*
* @default - Every hour
*/
readonly hour?: string;

/**
* The day of the month to run this rule at
*
* @default - Every day of the month
*/
readonly day?: string;

/**
* The month to run this rule at
*
* @default - Every month
*/
readonly month?: string;

/**
* The day of the week to run this rule at
*
* @default - Any day of the week
*/
readonly weekDay?: string;
}

class LiteralSchedule extends Schedule {
constructor(public readonly expressionString: string) {
super();
}
}

function fallback<T>(x: T | undefined, def: T): T {
return x === undefined ? def : x;
}
Loading

0 comments on commit 3ae7511

Please sign in to comment.