diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.js.snapshot/cdk-integ-opensearch.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.js.snapshot/cdk-integ-opensearch.template.json index bac7fe9a75f75..6cbe7fd7f64ee 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.js.snapshot/cdk-integ-opensearch.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.js.snapshot/cdk-integ-opensearch.template.json @@ -152,6 +152,18 @@ }, "NodeToNodeEncryptionOptions": { "Enabled": true + }, + "OffPeakWindowOptions": { + "Enabled": true, + "OffPeakWindow": { + "WindowStartTime": { + "Hours": 20, + "Minutes": 0 + } + } + }, + "SoftwareUpdateOptions": { + "AutoSoftwareUpdateEnabled": true } }, "DependsOn": [ @@ -458,6 +470,18 @@ }, "NodeToNodeEncryptionOptions": { "Enabled": true + }, + "OffPeakWindowOptions": { + "Enabled": true, + "OffPeakWindow": { + "WindowStartTime": { + "Hours": 20, + "Minutes": 0 + } + } + }, + "SoftwareUpdateOptions": { + "AutoSoftwareUpdateEnabled": true } }, "DependsOn": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ts index 68b8b9bd8bd5d..f93ef0bacea77 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ts @@ -41,6 +41,12 @@ class TestStack extends Stack { capacity: { multiAzWithStandbyEnabled: false, }, + offPeakWindowEnabled: true, + offPeakWindowStart: { + hours: 20, + minutes: 0, + }, + enableAutoSoftwareUpdate: true, }; // create 2 domains to ensure that Cloudwatch Log Group policy names dont conflict diff --git a/packages/aws-cdk-lib/aws-opensearchservice/README.md b/packages/aws-cdk-lib/aws-opensearchservice/README.md index 243d58f30dad0..a81451453514c 100644 --- a/packages/aws-cdk-lib/aws-opensearchservice/README.md +++ b/packages/aws-cdk-lib/aws-opensearchservice/README.md @@ -396,4 +396,40 @@ const domain = new Domain(this, 'Domain', { dataNodes: 3, }, }); +``` + +## Define off-peak windows + +The domain can be configured to use a daily 10-hour window considered as off-peak hours. + +Off-peak windows were introduced on February 16, 2023. +All domains created before this date have the off-peak window disabled by default. +You must manually enable and configure the off-peak window for these domains. +All domains created after this date will have the off-peak window enabled by default. +You can't disable the off-peak window for a domain after it's enabled. + +> Visit [Defining off-peak windows for Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/off-peak.html) for more details. + +```ts +const domain = new Domain(this, 'Domain', { + version: EngineVersion.OPENSEARCH_1_3, + offPeakWindowEnabled: true, // can be omitted if offPeakWindowStart is set + offPeakWindowStart: { + hours: 20, + minutes: 0, + }, +}); +``` + +## Configuring service software updates + +The domain can be configured to use service software updates. + +> Visit [Service software updates in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/service-software.html) for more details. + +```ts +const domain = new Domain(this, 'Domain', { + version: EngineVersion.OPENSEARCH_1_3, + enableAutoSoftwareUpdate: true, +}); ``` \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts b/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts index 7d26800e76bf3..ce260e1d8dc3f 100644 --- a/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts +++ b/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts @@ -328,6 +328,22 @@ export interface CustomEndpointOptions { readonly hostedZone?: route53.IHostedZone; } +export interface WindowStartTime { + /** + * The start hour of the window in Coordinated Universal Time (UTC), using 24-hour time. + * For example, 17 refers to 5:00 P.M. UTC. + * + * @default - 22 + */ + readonly hours: number; + /** + * The start minute of the window, in UTC. + * + * @default - 0 + */ + readonly minutes: number; +} + /** * Properties for an Amazon OpenSearch Service domain. */ @@ -493,6 +509,7 @@ export interface DomainProps { * domain resource, use the EnableVersionUpgrade update policy. * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html#cfn-attributes-updatepolicy-upgradeopensearchdomain + * * @default - false */ readonly enableVersionUpgrade?: boolean; @@ -508,9 +525,44 @@ export interface DomainProps { * To configure a custom domain configure these options * * If you specify a Route53 hosted zone it will create a CNAME record and use DNS validation for the certificate + * * @default - no custom domain endpoint will be configured */ readonly customEndpoint?: CustomEndpointOptions; + + /** + * Options for enabling a domain's off-peak window, during which OpenSearch Service can perform mandatory + * configuration changes on the domain. + * + * Off-peak windows were introduced on February 16, 2023. + * All domains created before this date have the off-peak window disabled by default. + * You must manually enable and configure the off-peak window for these domains. + * All domains created after this date will have the off-peak window enabled by default. + * You can't disable the off-peak window for a domain after it's enabled. + * + * @see https://docs.aws.amazon.com/it_it/AWSCloudFormation/latest/UserGuide/aws-properties-opensearchservice-domain-offpeakwindow.html + * + * @default - Disabled for domains created before February 16, 2023. Enabled for domains created after. Enabled if `offPeakWindowStart` is set. + */ + readonly offPeakWindowEnabled?: boolean; + + /** + * Start time for the off-peak window, in Coordinated Universal Time (UTC). + * The window length will always be 10 hours, so you can't specify an end time. + * For example, if you specify 11:00 P.M. UTC as a start time, the end time will automatically be set to 9:00 A.M. + * + * @default - 10:00 P.M. local time + */ + readonly offPeakWindowStart?: WindowStartTime; + + /** + * Specifies whether automatic service software updates are enabled for the domain. + * + * @see https://docs.aws.amazon.com/it_it/AWSCloudFormation/latest/UserGuide/aws-properties-opensearchservice-domain-softwareupdateoptions.html + * + * @default - false + */ + readonly enableAutoSoftwareUpdate?: boolean; } /** @@ -1089,7 +1141,6 @@ abstract class DomainBase extends cdk.Resource implements IDomain { return grant; } - } /** @@ -1558,6 +1609,11 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { } } + const offPeakWindowEnabled = props.offPeakWindowEnabled ?? props.offPeakWindowStart !== undefined; + if (offPeakWindowEnabled) { + this.validateWindowStartTime(props.offPeakWindowStart); + } + // Create the domain this.domain = new CfnDomain(this, 'Resource', { domainName: this.physicalName, @@ -1632,6 +1688,18 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { } : undefined, advancedOptions: props.advancedOptions, + offPeakWindowOptions: offPeakWindowEnabled ? { + enabled: offPeakWindowEnabled, + offPeakWindow: { + windowStartTime: props.offPeakWindowStart ?? { + hours: 22, + minutes: 0, + }, + }, + } : undefined, + softwareUpdateOptions: props.enableAutoSoftwareUpdate ? { + autoSoftwareUpdateEnabled: props.enableAutoSoftwareUpdate, + } : undefined, }); this.domain.applyRemovalPolicy(props.removalPolicy); @@ -1689,6 +1757,20 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { } } + /** + * Validate windowStartTime property according to + * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-opensearchservice-domain-windowstarttime.html + */ + private validateWindowStartTime(windowStartTime?: WindowStartTime) { + if (!windowStartTime) return; + if (windowStartTime.hours < 0 || windowStartTime.hours > 23) { + throw new Error(`Hours must be a value between 0 and 23, but got ${windowStartTime.hours}.`); + } + if (windowStartTime.minutes < 0 || windowStartTime.minutes > 59) { + throw new Error(`Minutes must be a value between 0 and 59, but got ${windowStartTime.minutes}.`); + } + } + /** * Manages network connections to the domain. This will throw an error in case the domain * is not placed inside a VPC. diff --git a/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts b/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts index ecb9c8a970a09..e708cb921d591 100644 --- a/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts +++ b/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts @@ -1980,6 +1980,113 @@ each(testedOpenSearchVersions).describe('cognito dashboards auth', (engineVersio }); }); +each(testedOpenSearchVersions).describe('offPeakWindow and softwareUpdateOptions', (engineVersion) => { + test('with offPeakWindowStart and offPeakWindowEnabled', () => { + new Domain(stack, 'Domain', { + version: engineVersion, + offPeakWindowEnabled: true, + offPeakWindowStart: { + hours: 10, + minutes: 30, + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::OpenSearchService::Domain', { + OffPeakWindowOptions: { + Enabled: true, + OffPeakWindow: { + WindowStartTime: { + Hours: 10, + Minutes: 30, + }, + }, + }, + }); + }); + + test('with offPeakWindowStart only', () => { + new Domain(stack, 'Domain', { + version: engineVersion, + offPeakWindowStart: { + hours: 10, + minutes: 30, + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::OpenSearchService::Domain', { + OffPeakWindowOptions: { + Enabled: true, + OffPeakWindow: { + WindowStartTime: { + Hours: 10, + Minutes: 30, + }, + }, + }, + }); + }); + + test('with offPeakWindowOptions default start time', () => { + new Domain(stack, 'Domain', { + version: engineVersion, + offPeakWindowEnabled: true, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::OpenSearchService::Domain', { + OffPeakWindowOptions: { + Enabled: true, + OffPeakWindow: { + WindowStartTime: { + Hours: 22, + Minutes: 0, + }, + }, + }, + }); + }); + + test('with autoSoftwareUpdateEnabled', () => { + new Domain(stack, 'Domain', { + version: engineVersion, + enableAutoSoftwareUpdate: true, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::OpenSearchService::Domain', { + SoftwareUpdateOptions: { + AutoSoftwareUpdateEnabled: true, + }, + }); + }); + + test('with invalid offPeakWindowStart', () => { + expect(() => { + new Domain(stack, 'Domain1', { + version: engineVersion, + offPeakWindowEnabled: true, + offPeakWindowStart: { + hours: 50, + minutes: 0, + }, + }); + }).toThrow( + /Hours must be a value between 0 and 23/, + ); + + expect(() => { + new Domain(stack, 'Domain2', { + version: engineVersion, + offPeakWindowEnabled: true, + offPeakWindowStart: { + hours: 10, + minutes: 90, + }, + }); + }).toThrow( + /Minutes must be a value between 0 and 59/, + ); + }); +}); + function testGrant( expectedActions: string[], invocation: (user: iam.IPrincipal, domain: Domain) => void,