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(scheduler): flexible time windows #28098

Merged
merged 20 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ff675c0
feat(scheduler): add support for flexible time window
sakurai-ryo Nov 21, 2023
85b60d4
feat(scheduler): add warning
sakurai-ryo Nov 22, 2023
5f3496b
Merge branch 'master' of https://github.com/sakurai-ryo/aws-cdk into …
sakurai-ryo Nov 22, 2023
b71fd8e
feat(scheduler): fix build failed
sakurai-ryo Nov 22, 2023
70362d5
Merge branch 'master' of https://github.com/sakurai-ryo/aws-cdk into …
sakurai-ryo Nov 24, 2023
21c4331
feat(scheduler): fixed for the review
sakurai-ryo Nov 24, 2023
2a2b00d
feat(scheduler): fix build
sakurai-ryo Nov 24, 2023
24cebc1
feat(scheduler): fix for the review
sakurai-ryo Nov 24, 2023
e2ce811
Merge branch 'master' of https://github.com/sakurai-ryo/aws-cdk into …
sakurai-ryo Dec 6, 2023
8fc8d33
Merge branch 'master' of https://github.com/sakurai-ryo/aws-cdk into …
sakurai-ryo Dec 8, 2023
443fa09
feat(scheduler): refactor class
sakurai-ryo Dec 8, 2023
9af2ec8
feat(scheduler): fix build
sakurai-ryo Dec 8, 2023
a8c279a
feat(code): delete new line
sakurai-ryo Dec 8, 2023
ba4630b
feat(scheduler): change property names
sakurai-ryo Dec 8, 2023
540561c
feat(scheduler): fix build
sakurai-ryo Dec 8, 2023
8169a6d
Merge branch 'master' of https://github.com/sakurai-ryo/aws-cdk into …
sakurai-ryo Dec 13, 2023
f509ef6
feat(scheduler): fix for the review
sakurai-ryo Dec 13, 2023
461f5ba
feat(scheduler): fix tests
sakurai-ryo Dec 13, 2023
e990c2a
Merge branch 'master' of https://github.com/sakurai-ryo/aws-cdk into …
sakurai-ryo Dec 14, 2023
fb820e9
Merge branch 'main' into feat-scheduler-flex-window
kaizencc Dec 15, 2023
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
21 changes: 19 additions & 2 deletions packages/@aws-cdk/aws-scheduler-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ of millions of tasks across many AWS services without provisioning or managing u
2. **Targets**: A target is an API operation that EventBridge Scheduler calls on your behalf every time your schedule runs. EventBridge Scheduler
supports two types of targets: templated targets and universal targets. Templated targets invoke common API operations across a core groups of
services. For example, EventBridge Scheduler supports templated targets for invoking AWS Lambda Function or starting execution of Step Function state
machine. For API operations that are not supported by templated targets you can use customizeable universal targets. Universal targets support calling
machine. For API operations that are not supported by templated targets you can use customizable universal targets. Universal targets support calling
more than 6,000 API operations across over 270 AWS services.
3. **Schedule Group**: A schedule group is an Amazon EventBridge Scheduler resource that you use to organize your schedules. Your AWS account comes
with a default scheduler group. A new schedule will always be added to a scheduling group. If you do not provide a scheduling group to add to, it
Expand Down Expand Up @@ -157,7 +157,7 @@ new Schedule(this, 'Schedule', {

The `@aws-cdk/aws-scheduler-targets-alpha` module includes classes that implement the `IScheduleTarget` interface for
various AWS services. EventBridge Scheduler supports two types of targets: templated targets invoke common API
operations across a core groups of services, and customizeable universal targets that you can use to call more
operations across a core groups of services, and customizable universal targets that you can use to call more
than 6,000 operations across over 270 services. A list of supported targets can be found at `@aws-cdk/aws-scheduler-targets-alpha`.

### Input
Expand Down Expand Up @@ -241,6 +241,23 @@ const schedule = new Schedule(this, 'Schedule', {

> Visit [Data protection in Amazon EventBridge Scheduler](https://docs.aws.amazon.com/scheduler/latest/UserGuide/data-protection.html) for more details.

## Configuring flexible time windows

You can configure flexible time windows by specifying the `timeWindow` property.
Flexible time windows is disabled by default.

```ts
declare const target: targets.LambdaInvoke;

const schedule = new Schedule(this, 'Schedule', {
schedule: ScheduleExpression.rate(Duration.hours(12)),
target,
timeWindow: TimeWindow.flexible(Duration.hours(10)),
});
```

> Visit [Configuring flexible time windows](https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-schedule-flexible-time-windows.html) for more details.

## Error-handling

You can configure how your schedule handles failures, when EventBridge Scheduler is unable to deliver an event
Expand Down
58 changes: 57 additions & 1 deletion packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,47 @@ export interface ScheduleTargetProps {
readonly retryAttempts?: number;
}

/**
* A time window during which EventBridge Scheduler invokes the schedule.
*/
export class TimeWindow {
/**
* TimeWindow is disabled.
*/
public static off(): TimeWindow {
return new TimeWindow('OFF');
}

/**
* TimeWindow is enabled.
*/
public static flexible(maxWindow: Duration): TimeWindow {
if (maxWindow.toMinutes() < 1 || maxWindow.toMinutes() > 1440) {
throw new Error(`The provided duration must be between 1 minute and 1440 minutes, got ${maxWindow.toMinutes()}`);
}
return new TimeWindow('FLEXIBLE', maxWindow);
}

/**
* Determines whether the schedule is invoked within a flexible time window.
*/
public readonly mode: 'OFF' | 'FLEXIBLE';

/**
* The maximum time window during which the schedule can be invoked.
*
* Must be between 1 to 1440 minutes.
*
* @default - no value
*/
public readonly maxWindow?: Duration;

private constructor(mode: 'OFF' | 'FLEXIBLE', maxWindow?: Duration) {
this.mode = mode;
this.maxWindow = maxWindow;
}
}

/**
* Construction properties for `Schedule`.
*/
Expand Down Expand Up @@ -104,6 +145,7 @@ export interface ScheduleProps {

/**
* Indicates whether the schedule is enabled.
*
* @default true
*/
readonly enabled?: boolean;
Expand All @@ -115,6 +157,15 @@ export interface ScheduleProps {
*/
readonly key?: kms.IKey;

/**
* A time window during which EventBridge Scheduler invokes the schedule.
*
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-schedule-flexible-time-windows.html
*
* @default TimeWindow.off()
*/
readonly timeWindow?: TimeWindow;

/**
* The date, in UTC, after which the schedule can begin invoking its target.
* EventBridge Scheduler ignores start for one-time schedules.
Expand Down Expand Up @@ -270,11 +321,16 @@ export class Schedule extends Resource implements ISchedule {

this.retryPolicy = targetConfig.retryPolicy;

const flexibleTimeWindow = props.timeWindow ?? TimeWindow.off();

this.validateTimeFrame(props.start, props.end);

const resource = new CfnSchedule(this, 'Resource', {
name: this.physicalName,
flexibleTimeWindow: { mode: 'OFF' },
flexibleTimeWindow: {
mode: flexibleTimeWindow.mode,
maximumWindowInMinutes: flexibleTimeWindow.maxWindow?.toMinutes(),
},
scheduleExpression: props.schedule.expressionString,
scheduleExpressionTimezone: props.schedule.timeZone?.timezoneName,
groupName: this.group?.groupName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as targets from '@aws-cdk/aws-scheduler-targets-alpha';
import { App, Stack, TimeZone, Duration } from 'aws-cdk-lib';
import { ScheduleExpression, ScheduleTargetInput, ContextAttribute, Group, Schedule } from '@aws-cdk/aws-scheduler-alpha';
import { ScheduleExpression, ScheduleTargetInput, ContextAttribute, Group, Schedule, TimeWindow } from '@aws-cdk/aws-scheduler-alpha';

class Fixture extends cdk.Stack {
constructor(scope: Construct, id: string) {
Expand Down

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

Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,37 @@
}
}
},
"UseFlexibleTimeWindowBF55D3ED": {
"Type": "AWS::Scheduler::Schedule",
"Properties": {
"FlexibleTimeWindow": {
"MaximumWindowInMinutes": 10,
"Mode": "FLEXIBLE"
},
"ScheduleExpression": "rate(12 hours)",
"ScheduleExpressionTimezone": "Etc/UTC",
"State": "ENABLED",
"Target": {
"Arn": {
"Fn::GetAtt": [
"Function76856677",
"Arn"
]
},
"Input": "\"Input Text\"",
"RetryPolicy": {
"MaximumEventAgeInSeconds": 180,
"MaximumRetryAttempts": 3
},
"RoleArn": {
"Fn::GetAtt": [
"Role1ABCC5F0",
"Arn"
]
}
}
}
},
"ScheduleWithTimeFrameC1C8BDCC": {
"Type": "AWS::Scheduler::Schedule",
"Properties": {
Expand Down

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

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

6 changes: 6 additions & 0 deletions packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ new scheduler.Schedule(stack, 'CustomerKmsSchedule', {
key,
});

new scheduler.Schedule(stack, 'UseFlexibleTimeWindow', {
schedule: expression,
target: target,
timeWindow: scheduler.TimeWindow.flexible(cdk.Duration.minutes(10)),
});

const currentYear = new Date().getFullYear();
new scheduler.Schedule(stack, 'ScheduleWithTimeFrame', {
schedule: expression,
Expand Down
56 changes: 55 additions & 1 deletion packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Template } from 'aws-cdk-lib/assertions';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { IScheduleTarget, Schedule, ScheduleTargetConfig } from '../lib';
import { IScheduleTarget, Schedule, ScheduleTargetConfig, TimeWindow } from '../lib';
import { ScheduleExpression } from '../lib/schedule-expression';

class SomeLambdaTarget implements IScheduleTarget {
Expand Down Expand Up @@ -161,4 +161,58 @@ describe('Schedule', () => {
}).toThrow(`start must precede end, got start: ${start}, end: ${end}`);
});
});

describe('flexibleTimeWindow', () => {
test('flexibleTimeWindow mode is set to OFF by default', () => {
// WHEN
new Schedule(stack, 'TestSchedule', {
schedule: expr,
target: new SomeLambdaTarget(func, role),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::Scheduler::Schedule', {
FlexibleTimeWindow: {
Mode: 'OFF',
},
});
});

test('flexibleTimeWindow mode can be set to FLEXIBLE', () => {
// WHEN
new Schedule(stack, 'TestSchedule', {
schedule: expr,
target: new SomeLambdaTarget(func, role),
timeWindow: TimeWindow.flexible(Duration.minutes(1440)),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::Scheduler::Schedule', {
FlexibleTimeWindow: {
Mode: 'FLEXIBLE',
MaximumWindowInMinutes: 1440,
},
});
});

test('throw error when maximumWindowInMinutes is greater than 1440', () => {
expect(() => {
new Schedule(stack, 'TestSchedule', {
schedule: expr,
target: new SomeLambdaTarget(func, role),
timeWindow: TimeWindow.flexible(Duration.minutes(1441)),
});
}).toThrow('The provided duration must be between 1 minute and 1440 minutes, got 1441');
});

test('throw error when maximumWindowInMinutes is less than 1', () => {
expect(() => {
new Schedule(stack, 'TestSchedule', {
schedule: expr,
target: new SomeLambdaTarget(func, role),
timeWindow: TimeWindow.flexible(Duration.minutes(0)),
});
}).toThrow('The provided duration must be between 1 minute and 1440 minutes, got 0');
});
});
});