From e364d2b6808fd703600bff07634c4ac3486f6367 Mon Sep 17 00:00:00 2001 From: Matsuda Date: Tue, 24 Dec 2024 07:14:52 +0900 Subject: [PATCH] fix(opensearch): add I4I and R7GD to list of OpenSearch nodes not requiring EBS volumes (#32592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Issue # (if applicable) Closes #32070. Closes #32138. ### Reason for this change `I4I` and `R7GD` instances don't require ebs volumes. But at the moment, a domain with `I4I` and `R7GD` instances cannot be deployed. ### Description of changes Added `I4I` and `R7GD` to not requiring EBS volumes instances list. ### Describe any new or updated permissions being added Add unit tests and integ tests. ### Description of how you validated changes ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- ...nteg-opensearch-instance-store.assets.json | 4 +- ...eg-opensearch-instance-store.template.json | 62 ++++++++++- .../manifest.json | 20 +++- .../tree.json | 100 +++++++++++++++++- .../test/integ.opensearch.ebs.ts | 33 +++--- .../aws-opensearchservice/lib/domain.ts | 37 +++++-- .../aws-opensearchservice/test/domain.test.ts | 59 +++-------- 7 files changed, 235 insertions(+), 80 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/cdk-integ-opensearch-instance-store.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/cdk-integ-opensearch-instance-store.assets.json index 28a52d128b6a1..133cf7ebe2aa9 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/cdk-integ-opensearch-instance-store.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/cdk-integ-opensearch-instance-store.assets.json @@ -1,7 +1,7 @@ { "version": "38.0.1", "files": { - "808dff0157a81a757437988ed8e5134982484f830ee8f11135a7009066356f0c": { + "beaba38779e7193b6d3c7408d8074a8cd0116f3852856626918653267cd4a392": { "source": { "path": "cdk-integ-opensearch-instance-store.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "808dff0157a81a757437988ed8e5134982484f830ee8f11135a7009066356f0c.json", + "objectKey": "beaba38779e7193b6d3c7408d8074a8cd0116f3852856626918653267cd4a392.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/cdk-integ-opensearch-instance-store.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/cdk-integ-opensearch-instance-store.template.json index cc624773ae4a0..f16d02724f73f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/cdk-integ-opensearch-instance-store.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/cdk-integ-opensearch-instance-store.template.json @@ -1,6 +1,6 @@ { "Resources": { - "Domain66AC69E0": { + "Domain19FCBCB91": { "Type": "AWS::OpenSearchService::Domain", "Properties": { "ClusterConfig": { @@ -20,7 +20,65 @@ "EncryptionAtRestOptions": { "Enabled": false }, - "EngineVersion": "OpenSearch_2.5", + "EngineVersion": "OpenSearch_2.17", + "LogPublishingOptions": {}, + "NodeToNodeEncryptionOptions": { + "Enabled": false + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "Domain2644FE48C": { + "Type": "AWS::OpenSearchService::Domain", + "Properties": { + "ClusterConfig": { + "DedicatedMasterEnabled": false, + "InstanceCount": 1, + "InstanceType": "i4i.xlarge.search", + "MultiAZWithStandbyEnabled": false, + "ZoneAwarenessEnabled": false + }, + "DomainEndpointOptions": { + "EnforceHTTPS": false, + "TLSSecurityPolicy": "Policy-Min-TLS-1-0-2019-07" + }, + "EBSOptions": { + "EBSEnabled": false + }, + "EncryptionAtRestOptions": { + "Enabled": false + }, + "EngineVersion": "OpenSearch_2.17", + "LogPublishingOptions": {}, + "NodeToNodeEncryptionOptions": { + "Enabled": false + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "Domain3BE108C7A": { + "Type": "AWS::OpenSearchService::Domain", + "Properties": { + "ClusterConfig": { + "DedicatedMasterEnabled": false, + "InstanceCount": 1, + "InstanceType": "r7gd.xlarge.search", + "MultiAZWithStandbyEnabled": false, + "ZoneAwarenessEnabled": false + }, + "DomainEndpointOptions": { + "EnforceHTTPS": false, + "TLSSecurityPolicy": "Policy-Min-TLS-1-0-2019-07" + }, + "EBSOptions": { + "EBSEnabled": false + }, + "EncryptionAtRestOptions": { + "Enabled": false + }, + "EngineVersion": "OpenSearch_2.17", "LogPublishingOptions": {}, "NodeToNodeEncryptionOptions": { "Enabled": false diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/manifest.json index 65adfcd240404..9399bceb10882 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/manifest.json @@ -16,10 +16,9 @@ "templateFile": "cdk-integ-opensearch-instance-store.template.json", "terminationProtection": false, "validateOnSynth": false, - "notificationArns": [], "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/808dff0157a81a757437988ed8e5134982484f830ee8f11135a7009066356f0c.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/beaba38779e7193b6d3c7408d8074a8cd0116f3852856626918653267cd4a392.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -35,10 +34,22 @@ "cdk-integ-opensearch-instance-store.assets" ], "metadata": { - "/cdk-integ-opensearch-instance-store/Domain/Resource": [ + "/cdk-integ-opensearch-instance-store/Domain1/Resource": [ { "type": "aws:cdk:logicalId", - "data": "Domain66AC69E0" + "data": "Domain19FCBCB91" + } + ], + "/cdk-integ-opensearch-instance-store/Domain2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Domain2644FE48C" + } + ], + "/cdk-integ-opensearch-instance-store/Domain3/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Domain3BE108C7A" } ], "/cdk-integ-opensearch-instance-store/BootstrapVersion": [ @@ -71,7 +82,6 @@ "templateFile": "IntegDefaultTestDeployAssert4E6713E1.template.json", "terminationProtection": false, "validateOnSynth": false, - "notificationArns": [], "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/tree.json index 4d17e03744c2f..7c97b949dd18b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.js.snapshot/tree.json @@ -8,13 +8,13 @@ "id": "cdk-integ-opensearch-instance-store", "path": "cdk-integ-opensearch-instance-store", "children": { - "Domain": { - "id": "Domain", - "path": "cdk-integ-opensearch-instance-store/Domain", + "Domain1": { + "id": "Domain1", + "path": "cdk-integ-opensearch-instance-store/Domain1", "children": { "Resource": { "id": "Resource", - "path": "cdk-integ-opensearch-instance-store/Domain/Resource", + "path": "cdk-integ-opensearch-instance-store/Domain1/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::OpenSearchService::Domain", "aws:cdk:cloudformation:props": { @@ -35,7 +35,97 @@ "encryptionAtRestOptions": { "enabled": false }, - "engineVersion": "OpenSearch_2.5", + "engineVersion": "OpenSearch_2.17", + "logPublishingOptions": {}, + "nodeToNodeEncryptionOptions": { + "enabled": false + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_opensearchservice.CfnDomain", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_opensearchservice.Domain", + "version": "0.0.0" + } + }, + "Domain2": { + "id": "Domain2", + "path": "cdk-integ-opensearch-instance-store/Domain2", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-integ-opensearch-instance-store/Domain2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::OpenSearchService::Domain", + "aws:cdk:cloudformation:props": { + "clusterConfig": { + "dedicatedMasterEnabled": false, + "instanceCount": 1, + "instanceType": "i4i.xlarge.search", + "multiAzWithStandbyEnabled": false, + "zoneAwarenessEnabled": false + }, + "domainEndpointOptions": { + "enforceHttps": false, + "tlsSecurityPolicy": "Policy-Min-TLS-1-0-2019-07" + }, + "ebsOptions": { + "ebsEnabled": false + }, + "encryptionAtRestOptions": { + "enabled": false + }, + "engineVersion": "OpenSearch_2.17", + "logPublishingOptions": {}, + "nodeToNodeEncryptionOptions": { + "enabled": false + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_opensearchservice.CfnDomain", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_opensearchservice.Domain", + "version": "0.0.0" + } + }, + "Domain3": { + "id": "Domain3", + "path": "cdk-integ-opensearch-instance-store/Domain3", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-integ-opensearch-instance-store/Domain3/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::OpenSearchService::Domain", + "aws:cdk:cloudformation:props": { + "clusterConfig": { + "dedicatedMasterEnabled": false, + "instanceCount": 1, + "instanceType": "r7gd.xlarge.search", + "multiAzWithStandbyEnabled": false, + "zoneAwarenessEnabled": false + }, + "domainEndpointOptions": { + "enforceHttps": false, + "tlsSecurityPolicy": "Policy-Min-TLS-1-0-2019-07" + }, + "ebsOptions": { + "ebsEnabled": false + }, + "encryptionAtRestOptions": { + "enabled": false + }, + "engineVersion": "OpenSearch_2.17", "logPublishingOptions": {}, "nodeToNodeEncryptionOptions": { "enabled": false diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.ts index b0298a15b7af5..e42d44d041b71 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-opensearchservice/test/integ.opensearch.ebs.ts @@ -7,23 +7,24 @@ class TestStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - // deploy the latest opensearch domain with instance store only (no EBS) - const domainProps: opensearch.DomainProps = { - removalPolicy: RemovalPolicy.DESTROY, - version: opensearch.EngineVersion.OPENSEARCH_2_5, - // specify the instance type that supports instance store - capacity: { - multiAzWithStandbyEnabled: false, - dataNodeInstanceType: 'i4g.large.search', - dataNodes: 1, - }, - // force ebs configuration to be disabled - ebs: { - enabled: false, - }, - }; + const instanceTypes = ['i4g.large.search', 'i4i.xlarge.search', 'r7gd.xlarge.search']; - new opensearch.Domain(this, 'Domain', domainProps); + instanceTypes.forEach((instanceType, index) => { + new opensearch.Domain(this, `Domain${index + 1}`, { + removalPolicy: RemovalPolicy.DESTROY, + version: opensearch.EngineVersion.OPENSEARCH_2_17, + // specify the instance type that supports instance store + capacity: { + multiAzWithStandbyEnabled: false, + dataNodeInstanceType: instanceType, + dataNodes: 1, + }, + // force ebs configuration to be disabled + ebs: { + enabled: false, + }, + }); + }); } } diff --git a/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts b/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts index 83f7eb14f9906..f3603307afbb5 100644 --- a/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts +++ b/packages/aws-cdk-lib/aws-opensearchservice/lib/domain.ts @@ -1574,10 +1574,24 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { } } + const unSupportEbsInstanceType = [ + ec2.InstanceClass.I3, + ec2.InstanceClass.R6GD, + ec2.InstanceClass.I4G, + ec2.InstanceClass.I4I, + ec2.InstanceClass.IM4GN, + ec2.InstanceClass.R7GD, + ]; + + const supportInstanceStorageInstanceType = [ + ec2.InstanceClass.R3, + ...unSupportEbsInstanceType, + ]; + // Validate against instance type restrictions, per // https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-instance-types.html - if (isSomeInstanceType('i3', 'r6gd', 'i4g', 'im4gn') && ebsEnabled) { - throw new Error('I3, R6GD, I4G, and IM4GN instance types do not support EBS storage volumes.'); + if (isSomeInstanceType(...unSupportEbsInstanceType) && ebsEnabled) { + throw new Error(`${formatInstanceTypesList(unSupportEbsInstanceType, 'and')} instance types do not support EBS storage volumes.`); } if (isSomeInstanceType('m3', 'r3', 't2') && encryptionAtRestEnabled) { @@ -1592,10 +1606,10 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { throw new Error('T2 and T3 instance types do not support UltraWarm storage.'); } - // Only R3, I3, R6GD, I4G and IM4GN support instance storage, per + // Only R3, I3, R6GD, I4G, I4I, IM4GN and R7GD support instance storage, per // https://aws.amazon.com/opensearch-service/pricing/ - if (!ebsEnabled && !isEveryDatanodeInstanceType('r3', 'i3', 'r6gd', 'i4g', 'im4gn')) { - throw new Error('EBS volumes are required when using instance types other than R3, I3, R6GD, I4G, or IM4GN.'); + if (!ebsEnabled && !isEveryDatanodeInstanceType(...supportInstanceStorageInstanceType)) { + throw new Error(`EBS volumes are required when using instance types other than ${formatInstanceTypesList(supportInstanceStorageInstanceType, 'or')}.`); } // Only for a valid ebs volume configuration, per @@ -1898,7 +1912,7 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { identityPoolId: props.cognitoDashboardsAuth?.identityPoolId, roleArn: props.cognitoDashboardsAuth?.role.roleArn, userPoolId: props.cognitoDashboardsAuth?.userPoolId, - }: undefined, + } : undefined, vpcOptions: cfnVpcOptions, snapshotOptions: props.automatedSnapshotStartHour ? { automatedSnapshotStartHour: props.automatedSnapshotStartHour } @@ -2188,3 +2202,14 @@ function initializeInstanceType(defaultInstanceType: string, instanceType?: stri return defaultInstanceType; } } + +/** + * Format instance types list for error messages. + * + * @param instanceTypes List of instance types to format + * @param conjunction Word to use as the conjunction (e.g., 'and', 'or') + * @returns Formatted instance types list for error messages + */ +function formatInstanceTypesList(instanceTypes: string[], conjunction: string): string { + return instanceTypes.map((type) => type.toUpperCase()).join(', ').replace(/, ([^,]*)$/, ` ${conjunction} $1`); +} 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 6957800ab3c56..df0866c32a410 100644 --- a/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts +++ b/packages/aws-cdk-lib/aws-opensearchservice/test/domain.test.ts @@ -1964,47 +1964,24 @@ each(testedOpenSearchVersions).describe('custom error responses', (engineVersion })).toThrow(/Node-to-node encryption requires Elasticsearch version 6.0 or later or OpenSearch version 1.0 or later/); }); - test('error when I3, R6GD, I4G, and IM4GN instance types are specified with EBS enabled', () => { + test.each([ + 'i3.2xlarge.search', + 'r6gd.large.search', + 'im4gn.2xlarge.search', + 'i4g.large.search', + 'i4i.xlarge.search', + 'r7gd.xlarge.search', + ])('error when %4 instance types are specified with EBS enabled', (dataNodeInstanceType) => { expect(() => new Domain(stack, 'Domain2', { version: engineVersion, capacity: { - dataNodeInstanceType: 'i3.2xlarge.search', - }, - ebs: { - volumeSize: 100, - volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, - }, - })).toThrow(/I3, R6GD, I4G, and IM4GN instance types do not support EBS storage volumes./); - expect(() => new Domain(stack, 'Domain3', { - version: engineVersion, - capacity: { - dataNodeInstanceType: 'r6gd.large.search', - }, - ebs: { - volumeSize: 100, - volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, - }, - })).toThrow(/I3, R6GD, I4G, and IM4GN instance types do not support EBS storage volumes./); - expect(() => new Domain(stack, 'Domain4', { - version: engineVersion, - capacity: { - dataNodeInstanceType: 'im4gn.2xlarge.search', - }, - ebs: { - volumeSize: 100, - volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, - }, - })).toThrow(/I3, R6GD, I4G, and IM4GN instance types do not support EBS storage volumes./); - expect(() => new Domain(stack, 'Domain5', { - version: engineVersion, - capacity: { - dataNodeInstanceType: 'i4g.large.search', + dataNodeInstanceType, }, ebs: { volumeSize: 100, volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, }, - })).toThrow(/I3, R6GD, I4G, and IM4GN instance types do not support EBS storage volumes./); + })).toThrow(/I3, R6GD, I4G, I4I, IM4GN and R7GD instance types do not support EBS storage volumes./); }); test('error when m3, r3, or t2 instance types are specified with encryption at rest enabled', () => { @@ -2047,7 +2024,10 @@ each(testedOpenSearchVersions).describe('custom error responses', (engineVersion })).toThrow(/t2.micro.search instance type supports only Elasticsearch versions 1.5 and 2.3/); }); - test('error when any instance type other than R3, I3, R6GD, I4G, or IM4GN are specified without EBS enabled', () => { + test.each([ + 'm5.large.search', + 'r5.large.search', + ])('error when any instance type other than R3, I3, R6GD, I4I, I4G, IM4GN or R7GD are specified without EBS enabled', () => { expect(() => new Domain(stack, 'Domain1', { version: engineVersion, ebs: { @@ -2056,16 +2036,7 @@ each(testedOpenSearchVersions).describe('custom error responses', (engineVersion capacity: { masterNodeInstanceType: 'm5.large.search', }, - })).toThrow(/EBS volumes are required when using instance types other than R3, I3, R6GD, I4G, or IM4GN./); - expect(() => new Domain(stack, 'Domain2', { - version: engineVersion, - ebs: { - enabled: false, - }, - capacity: { - dataNodeInstanceType: 'r5.large.search', - }, - })).toThrow(/EBS volumes are required when using instance types other than R3, I3, R6GD, I4G, or IM4GN./); + })).toThrow(/EBS volumes are required when using instance types other than R3, I3, R6GD, I4G, I4I, IM4GN or R7GD./); }); test('can use compatible master instance types that does not have local storage when data node type is i3 or r6gd', () => {