From f25e8d3ec2b770caa345a575ea4bc8ba94b53e50 Mon Sep 17 00:00:00 2001 From: Sayali Gaikawad Date: Fri, 22 Dec 2023 15:29:49 -0800 Subject: [PATCH 1/5] Refactor infra-stack and remove entrypoint file Signed-off-by: Sayali Gaikawad --- bin/app.ts | 26 +- lib/infra/infra-stack.ts | 462 +++++++++++++----- lib/infra/remote-store-resources.ts | 4 +- lib/os-cluster-entrypoint.ts | 286 ----------- ...test.ts => opensearch-cluster-cdk.test.ts} | 256 +++++++--- 5 files changed, 550 insertions(+), 484 deletions(-) delete mode 100644 lib/os-cluster-entrypoint.ts rename test/{os-cluster.test.ts => opensearch-cluster-cdk.test.ts} (69%) diff --git a/bin/app.ts b/bin/app.ts index 43c31133ad2..f0109085be1 100644 --- a/bin/app.ts +++ b/bin/app.ts @@ -8,12 +8,34 @@ compatible open source license. */ import { App } from 'aws-cdk-lib'; import 'source-map-support/register'; -import { OsClusterEntrypoint } from '../lib/os-cluster-entrypoint'; +import { InfraStack } from '../lib/infra/infra-stack'; +import { NetworkStack } from '../lib/networking/vpc-stack'; const app = new App(); const region = app.node.tryGetContext('region') ?? process.env.CDK_DEFAULT_REGION; const account = app.node.tryGetContext('account') ?? process.env.CDK_DEFAULT_ACCOUNT; -new OsClusterEntrypoint(app, { +const suffix = `${app.node.tryGetContext('suffix')}`; +const networkStackSuffix = `${app.node.tryGetContext('networkStackSuffix')}`; + +let networkStackName = 'opensearch-network-stack'; +if (networkStackSuffix !== 'undefined') { + networkStackName = `opensearch-network-stack-${networkStackSuffix}`; +} +let infraStackName = 'opensearch-infra-stack'; +if (suffix !== 'undefined') { + infraStackName = `opensearch-infra-stack-${suffix}`; +} + +const networkStack = new NetworkStack(app, networkStackName, { + env: { account, region }, +}); + +// @ts-ignore +const infraStack = new InfraStack(app, infraStackName, { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, env: { account, region }, }); + +infraStack.addDependency(networkStack); diff --git a/lib/infra/infra-stack.ts b/lib/infra/infra-stack.ts index a778edeb41b..870f1cb324a 100644 --- a/lib/infra/infra-stack.ts +++ b/lib/infra/infra-stack.ts @@ -41,36 +41,55 @@ import { join } from 'path'; import { CloudwatchAgent } from '../cloudwatch/cloudwatch-agent'; import { ProcstatMetricDefinition } from '../cloudwatch/metrics-section'; import { InfraStackMonitoring } from '../monitoring/alarms'; -import { nodeConfig } from '../opensearch-config/node-config'; +import { + getArm64InstanceTypes, getVolumeType, getX64InstanceTypes, nodeConfig, +} from '../opensearch-config/node-config'; import { RemoteStoreResources } from './remote-store-resources'; -export interface infraProps extends StackProps { - readonly vpc: IVpc, +enum cpuArchEnum{ + X64='x64', + ARM64='arm64' +} + +const getInstanceType = (instanceType: string, arch: string) => { + if (arch === 'x64') { + if (instanceType !== 'undefined') { + return getX64InstanceTypes(instanceType); + } + return getX64InstanceTypes('r5.xlarge'); + } + if (instanceType !== 'undefined') { + return getArm64InstanceTypes(instanceType); + } + return getArm64InstanceTypes('r6g.xlarge'); +}; + +export interface InfraProps extends StackProps { + readonly vpcId: IVpc, readonly securityGroup: ISecurityGroup, - readonly opensearchVersion: string, - readonly cpuArch: string, - readonly cpuType: AmazonLinuxCpuType, - readonly securityDisabled: boolean, - readonly adminPassword: string, - readonly minDistribution: boolean, - readonly distributionUrl: string, - readonly dashboardsUrl: string, - readonly singleNodeCluster: boolean, - readonly managerNodeCount: number, - readonly dataNodeCount: number, - readonly ingestNodeCount: number, - readonly clientNodeCount: number, - readonly mlNodeCount: number, - readonly dataNodeStorage: number, - readonly mlNodeStorage: number, - readonly dataEc2InstanceType: InstanceType, - readonly mlEc2InstanceType: InstanceType, - readonly use50PercentHeap: boolean, - readonly isInternal: boolean, - readonly enableRemoteStore: boolean, - readonly storageVolumeType: EbsDeviceVolumeType, - readonly customRoleArn: string, - readonly jvmSysPropsString?: string, + readonly distVersion?: string, + readonly cpuArch?: string, + readonly securityDisabled?: boolean, + readonly adminPassword?: string, + readonly minDistribution?: boolean, + readonly distributionUrl?: string, + readonly dashboardsUrl?: string, + readonly singleNodeCluster?: boolean, + readonly managerNodeCount?: number, + readonly dataNodeCount?: number, + readonly ingestNodeCount?: number, + readonly clientNodeCount?: number, + readonly mlNodeCount?: number, + readonly dataNodeStorage?: number, + readonly mlNodeStorage?: number, + readonly dataInstanceType?: InstanceType, + readonly mlInstanceType?: InstanceType, + readonly use50PercentHeap?: boolean, + readonly isInternal?: boolean, + readonly enableRemoteStore?: boolean, + readonly storageVolumeType?: EbsDeviceVolumeType, + readonly customRoleArn?: string, + readonly jvmSysProps?: string, readonly additionalConfig?: string, readonly additionalOsdConfig?: string, readonly customConfigFiles?: string, @@ -80,7 +99,61 @@ export interface infraProps extends StackProps { export class InfraStack extends Stack { private instanceRole: Role; - constructor(scope: Stack, id: string, props: infraProps) { + private distVersion: string; + + private cpuArch: string; + + private securityDisabled: boolean; + + private adminPassword: string; + + private minDistribution: boolean; + + private distributionUrl: string; + + private dashboardsUrl: string; + + private singleNodeCluster: boolean; + + private managerNodeCount: number; + + private dataNodeCount: number | string; + + private ingestNodeCount: number | string; + + private clientNodeCount: number | string; + + private mlNodeCount: number | string; + + private dataNodeStorage: number; + + private mlNodeStorage: number; + + private dataInstanceType: InstanceType; + + private mlInstanceType: InstanceType; + + private use50PercentHeap: boolean; + + private isInternal: boolean; + + private enableRemoteStore: boolean; + + private storageVolumeType: EbsDeviceVolumeType; + + private customRoleArn: string; + + private jvmSysProps: string; + + private additionalConfig: string; + + private additionalOsdConfig: string; + + private customConfigFiles: string; + + private enableMonitoring: boolean; + + constructor(scope: Stack, id: string, props: InfraProps) { super(scope, id, props); let opensearchListener: NetworkListener; let dashboardsListener: NetworkListener; @@ -88,16 +161,159 @@ export class InfraStack extends Stack { let dataAsgCapacity: number; let clientNodeAsg: AutoScalingGroup; let seedConfig: string; - let hostType: InstanceType; let singleNodeInstance: Instance; + let instanceCpuType: AmazonLinuxCpuType; - const clusterLogGroup = new LogGroup(this, 'opensearchLogGroup', { - logGroupName: `${id}LogGroup/opensearch.log`, - retention: RetentionDays.ONE_MONTH, - removalPolicy: RemovalPolicy.DESTROY, - }); + // Properties and context variables checks + this.distVersion = `${props?.distVersion ?? scope.node.tryGetContext('distVersion')}`; + if (this.distVersion.toString() === 'undefined') { + throw new Error('distVersion parameter cannot be empty! Please provide the OpenSearch distribution version'); + } + + const securityDisabled = `${props?.securityDisabled ?? scope.node.tryGetContext('securityDisabled')}`; + if (securityDisabled !== 'true' && securityDisabled !== 'false') { + throw new Error('securityDisabled parameter is required to be set as - true or false'); + } + this.securityDisabled = securityDisabled === 'true'; + + this.adminPassword = this.securityDisabled ? '' : `${props?.adminPassword ?? scope.node.tryGetContext('adminPassword')}`; + if (!this.securityDisabled && Number.parseFloat(this.distVersion) >= 2.12 && this.adminPassword === 'undefined') { + throw new Error('adminPassword parameter is required to be set when security is enabled'); + } + + const minDistribution = `${props?.minDistribution ?? scope.node.tryGetContext('minDistribution')}`; + if (minDistribution !== 'true' && minDistribution !== 'false') { + throw new Error('minDistribution parameter is required to be set as - true or false'); + } else { + this.minDistribution = minDistribution === 'true'; + } + + this.distributionUrl = `${props?.distributionUrl ?? scope.node.tryGetContext('distributionUrl')}`; + if (this.distributionUrl.toString() === 'undefined') { + throw new Error('distributionUrl parameter is required. Please provide the OpenSearch distribution artifact url to download'); + } + + this.dashboardsUrl = `${props?.dashboardsUrl ?? scope.node.tryGetContext('dashboardsUrl')}`; + const dataInstanceType: InstanceType | string = `${props?.dataInstanceType ?? scope.node.tryGetContext('dataInstanceType')}`; + const mlInstanceType: InstanceType | string = `${props?.mlInstanceType ?? scope.node.tryGetContext('mlInstanceType')}`; + + this.cpuArch = `${props?.cpuArch ?? scope.node.tryGetContext('cpuArch')}`; + if (this.cpuArch === 'undefined') { + throw new Error('cpuArch parameter is required. Valid inputs: x64 or arm64'); + // @ts-ignore + } else if (Object.values(cpuArchEnum).includes(this.cpuArch.toString())) { + if (this.cpuArch.toString() === cpuArchEnum.X64) { + instanceCpuType = AmazonLinuxCpuType.X86_64; + this.dataInstanceType = getInstanceType(dataInstanceType, this.cpuArch.toString()); + this.mlInstanceType = getInstanceType(mlInstanceType, this.cpuArch.toString()); + } else { + instanceCpuType = AmazonLinuxCpuType.ARM_64; + this.dataInstanceType = getInstanceType(dataInstanceType, this.cpuArch.toString()); + this.mlInstanceType = getInstanceType(mlInstanceType, this.cpuArch.toString()); + } + } else { + throw new Error('Please provide a valid cpu architecture. The valid value can be either x64 or arm64'); + } + + const singleNodeCluster = `${props?.singleNodeCluster ?? scope.node.tryGetContext('singleNodeCluster')}`; + this.singleNodeCluster = singleNodeCluster === 'true'; + + const managerCount = `${props?.managerNodeCount ?? scope.node.tryGetContext('managerNodeCount')}`; + if (managerCount === 'undefined') { + this.managerNodeCount = 3; + } else { + this.managerNodeCount = parseInt(managerCount, 10); + } + + const dataNode = `${props?.dataNodeCount ?? scope.node.tryGetContext('dataNodeCount')}`; + if (dataNode === 'undefined') { + this.dataNodeCount = 2; + } else { + this.dataNodeCount = parseInt(dataNode, 10); + } + + const clientNode = `${props?.clientNodeCount ?? scope.node.tryGetContext('clientNodeCount')}`; + if (clientNode === 'undefined') { + this.clientNodeCount = 0; + } else { + this.clientNodeCount = parseInt(clientNode, 10); + } + + const ingestNode = `${props?.ingestNodeCount ?? scope.node.tryGetContext('ingestNodeCount')}`; + if (ingestNode === 'undefined') { + this.ingestNodeCount = 0; + } else { + this.ingestNodeCount = parseInt(ingestNode, 10); + } - if (props.customRoleArn === 'undefined') { + const mlNode = `${props?.mlNodeCount ?? scope.node.tryGetContext('mlNodeCount')}`; + if (mlNode === 'undefined') { + this.mlNodeCount = 0; + } else { + this.mlNodeCount = parseInt(mlNode, 10); + } + + const dataStorage = `${props?.dataNodeStorage ?? scope.node.tryGetContext('dataNodeStorage')}`; + if (dataStorage === 'undefined') { + this.dataNodeStorage = 100; + } else { + this.dataNodeStorage = parseInt(dataStorage, 10); + } + + const storageVolType = `${props?.storageVolumeType ?? scope.node.tryGetContext('storageVolumeType')}`; + if (storageVolType === 'undefined') { + // use gp2 volume by default + this.storageVolumeType = getVolumeType('gp2'); + } else { + this.storageVolumeType = getVolumeType(storageVolType); + } + + const mlStorage = `${props?.mlNodeStorage ?? scope.node.tryGetContext('mlNodeStorage')}`; + if (mlStorage === 'undefined') { + this.mlNodeStorage = 100; + } else { + this.mlNodeStorage = parseInt(mlStorage, 10); + } + + this.jvmSysProps = `${props?.jvmSysProps ?? scope.node.tryGetContext('jvmSysProps')}`; + + const additionalConf = `${props?.additionalConfig ?? scope.node.tryGetContext('additionalConfig')}`; + if (additionalConf !== 'undefined') { + try { + const jsonObj = JSON.parse(additionalConf); + this.additionalConfig = dump(jsonObj); + } catch (e) { + throw new Error(`Encountered following error while parsing additionalConfig json parameter: ${e}`); + } + } else { + this.additionalConfig = additionalConf; + } + + const additionalOsdConf = `${props?.additionalOsdConfig ?? scope.node.tryGetContext('additionalOsdConfig')}`; + if (additionalOsdConf.toString() !== 'undefined') { + try { + const jsonObj = JSON.parse(additionalOsdConf); + this.additionalOsdConfig = dump(jsonObj); + } catch (e) { + throw new Error(`Encountered following error while parsing additionalOsdConfig json parameter: ${e}`); + } + } else { + this.additionalOsdConfig = additionalOsdConf; + } + + this.customConfigFiles = `${props?.customConfigFiles ?? scope.node.tryGetContext('customConfigFiles')}`; + + const use50heap = `${props?.use50PercentHeap ?? scope.node.tryGetContext('use50PercentHeap')}`; + this.use50PercentHeap = use50heap === 'true'; + + const nlbScheme = `${props.isInternal ?? scope.node.tryGetContext('isInternal')}`; + this.isInternal = nlbScheme === 'true'; + + const monitoringAndAlarms = `${props?.enableMonitoring ?? scope.node.tryGetContext('enableMonitoring')}`; + this.enableMonitoring = monitoringAndAlarms === 'true'; + + this.customRoleArn = `${props?.customRoleArn ?? scope.node.tryGetContext('customRoleArn')}`; + if (this.customRoleArn === 'undefined') { this.instanceRole = new Role(this, 'instanceRole', { managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'), @@ -105,35 +321,43 @@ export class InfraStack extends Stack { assumedBy: new ServicePrincipal('ec2.amazonaws.com'), }); } else { - this.instanceRole = Role.fromRoleArn(this, 'custom-role-arn', `${props.customRoleArn}`); + this.instanceRole = Role.fromRoleArn(this, 'custom-role-arn', `${this.customRoleArn}`); } - if (props.enableRemoteStore) { + const remoteStore = `${props?.enableRemoteStore ?? scope.node.tryGetContext('enableRemoteStore')}`; + this.enableRemoteStore = remoteStore === 'true'; + if (this.enableRemoteStore) { // Remote Store needs an S3 bucket to be registered as snapshot repo // Add scoped bucket policy to the instance role attached to the EC2 const remoteStoreObj = new RemoteStoreResources(this); this.instanceRole.addToPolicy(remoteStoreObj.getRemoteStoreBucketPolicy()); } + const clusterLogGroup = new LogGroup(this, 'opensearchLogGroup', { + logGroupName: `${id}LogGroup/opensearch.log`, + retention: RetentionDays.ONE_MONTH, + removalPolicy: RemovalPolicy.DESTROY, + }); + let singleNodeInstanceType: InstanceType; - if (props.dataEc2InstanceType) { - singleNodeInstanceType = props.dataEc2InstanceType; - } else if (props.cpuType === AmazonLinuxCpuType.X86_64) { + if (dataInstanceType) { + singleNodeInstanceType = this.dataInstanceType; + } else if (instanceCpuType === AmazonLinuxCpuType.X86_64) { singleNodeInstanceType = InstanceType.of(InstanceClass.R5, InstanceSize.XLARGE); } else { singleNodeInstanceType = InstanceType.of(InstanceClass.R6G, InstanceSize.XLARGE); } - const defaultInstanceType = (props.cpuType === AmazonLinuxCpuType.X86_64) + const defaultInstanceType = (instanceCpuType === AmazonLinuxCpuType.X86_64) ? InstanceType.of(InstanceClass.C5, InstanceSize.XLARGE) : InstanceType.of(InstanceClass.C6G, InstanceSize.XLARGE); const nlb = new NetworkLoadBalancer(this, 'clusterNlb', { - vpc: props.vpc, - internetFacing: (!props.isInternal), + vpc: props.vpcId, + internetFacing: (!this.isInternal), crossZoneEnabled: true, }); - if (!props.securityDisabled && !props.minDistribution) { + if (!this.securityDisabled && !this.minDistribution) { opensearchListener = nlb.addListener('opensearch', { port: 443, protocol: Protocol.TCP, @@ -145,21 +369,21 @@ export class InfraStack extends Stack { }); } - if (props.dashboardsUrl !== 'undefined') { + if (this.dashboardsUrl !== 'undefined') { dashboardsListener = nlb.addListener('dashboards', { port: 8443, protocol: Protocol.TCP, }); } - if (props.singleNodeCluster) { + if (this.singleNodeCluster) { console.log('Single node value is true, creating single node configurations'); singleNodeInstance = new Instance(this, 'single-node-instance', { - vpc: props.vpc, + vpc: props.vpcId, instanceType: singleNodeInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - cpuType: props.cpuType, + cpuType: instanceCpuType, }), role: this.instanceRole, vpcSubnets: { @@ -168,9 +392,9 @@ export class InfraStack extends Stack { securityGroup: props.securityGroup, blockDevices: [{ deviceName: '/dev/xvda', - volume: BlockDeviceVolume.ebs(props.dataNodeStorage, { deleteOnTermination: true, volumeType: props.storageVolumeType }), + volume: BlockDeviceVolume.ebs(this.dataNodeStorage, { deleteOnTermination: true, volumeType: this.storageVolumeType }), }], - init: CloudFormationInit.fromElements(...InfraStack.getCfnInitElement(this, clusterLogGroup, props)), + init: CloudFormationInit.fromElements(...this.getCfnInitElement(this, clusterLogGroup)), initOptions: { ignoreFailures: false, }, @@ -183,7 +407,7 @@ export class InfraStack extends Stack { targets: [new InstanceTarget(singleNodeInstance)], }); - if (props.dashboardsUrl !== 'undefined') { + if (this.dashboardsUrl !== 'undefined') { // @ts-ignore dashboardsListener.addTargets('single-node-osd-target', { port: 5601, @@ -194,21 +418,21 @@ export class InfraStack extends Stack { value: singleNodeInstance.instancePrivateIp, }); } else { - if (props.managerNodeCount > 0) { - managerAsgCapacity = props.managerNodeCount - 1; - dataAsgCapacity = props.dataNodeCount; + if (this.managerNodeCount > 0) { + managerAsgCapacity = this.managerNodeCount - 1; + dataAsgCapacity = this.dataNodeCount; } else { - managerAsgCapacity = props.managerNodeCount; - dataAsgCapacity = props.dataNodeCount - 1; + managerAsgCapacity = this.managerNodeCount; + dataAsgCapacity = this.dataNodeCount - 1; } if (managerAsgCapacity > 0) { const managerNodeAsg = new AutoScalingGroup(this, 'managerNodeAsg', { - vpc: props.vpc, + vpc: props.vpcId, instanceType: defaultInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - cpuType: props.cpuType, + cpuType: instanceCpuType, }), role: this.instanceRole, maxCapacity: managerAsgCapacity, @@ -222,7 +446,7 @@ export class InfraStack extends Stack { deviceName: '/dev/xvda', volume: BlockDeviceVolume.ebs(50, { deleteOnTermination: true, volumeType: props.storageVolumeType }), }], - init: CloudFormationInit.fromElements(...InfraStack.getCfnInitElement(this, clusterLogGroup, props, 'manager')), + init: CloudFormationInit.fromElements(...this.getCfnInitElement(this, clusterLogGroup, 'manager')), initOptions: { ignoreFailures: false, }, @@ -237,11 +461,11 @@ export class InfraStack extends Stack { } const seedNodeAsg = new AutoScalingGroup(this, 'seedNodeAsg', { - vpc: props.vpc, - instanceType: (seedConfig === 'seed-manager') ? defaultInstanceType : props.dataEc2InstanceType, + vpc: props.vpcId, + instanceType: (seedConfig === 'seed-manager') ? defaultInstanceType : this.dataInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - cpuType: props.cpuType, + cpuType: instanceCpuType, }), role: this.instanceRole, maxCapacity: 1, @@ -254,9 +478,9 @@ export class InfraStack extends Stack { blockDevices: [{ deviceName: '/dev/xvda', // eslint-disable-next-line max-len - volume: (seedConfig === 'seed-manager') ? BlockDeviceVolume.ebs(50, { deleteOnTermination: true, volumeType: props.storageVolumeType }) : BlockDeviceVolume.ebs(props.dataNodeStorage, { deleteOnTermination: true, volumeType: props.storageVolumeType }), + volume: (seedConfig === 'seed-manager') ? BlockDeviceVolume.ebs(50, { deleteOnTermination: true, volumeType: props.storageVolumeType }) : BlockDeviceVolume.ebs(this.dataNodeStorage, { deleteOnTermination: true, volumeType: this.storageVolumeType }), }], - init: CloudFormationInit.fromElements(...InfraStack.getCfnInitElement(this, clusterLogGroup, props, seedConfig)), + init: CloudFormationInit.fromElements(...this.getCfnInitElement(this, clusterLogGroup, seedConfig)), initOptions: { ignoreFailures: false, }, @@ -266,11 +490,11 @@ export class InfraStack extends Stack { Tags.of(seedNodeAsg).add('role', 'manager'); const dataNodeAsg = new AutoScalingGroup(this, 'dataNodeAsg', { - vpc: props.vpc, - instanceType: props.dataEc2InstanceType, + vpc: props.vpcId, + instanceType: this.dataInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - cpuType: props.cpuType, + cpuType: instanceCpuType, }), role: this.instanceRole, maxCapacity: dataAsgCapacity, @@ -282,9 +506,9 @@ export class InfraStack extends Stack { securityGroup: props.securityGroup, blockDevices: [{ deviceName: '/dev/xvda', - volume: BlockDeviceVolume.ebs(props.dataNodeStorage, { deleteOnTermination: true, volumeType: props.storageVolumeType }), + volume: BlockDeviceVolume.ebs(this.dataNodeStorage, { deleteOnTermination: true, volumeType: this.storageVolumeType }), }], - init: CloudFormationInit.fromElements(...InfraStack.getCfnInitElement(this, clusterLogGroup, props, 'data')), + init: CloudFormationInit.fromElements(...this.getCfnInitElement(this, clusterLogGroup, 'data')), initOptions: { ignoreFailures: false, }, @@ -293,29 +517,29 @@ export class InfraStack extends Stack { }); Tags.of(dataNodeAsg).add('role', 'data'); - if (props.clientNodeCount === 0) { + if (this.clientNodeCount === 0) { clientNodeAsg = dataNodeAsg; } else { clientNodeAsg = new AutoScalingGroup(this, 'clientNodeAsg', { - vpc: props.vpc, + vpc: props.vpcId, instanceType: defaultInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - cpuType: props.cpuType, + cpuType: instanceCpuType, }), role: this.instanceRole, - maxCapacity: props.clientNodeCount, - minCapacity: props.clientNodeCount, - desiredCapacity: props.clientNodeCount, + maxCapacity: this.clientNodeCount, + minCapacity: this.clientNodeCount, + desiredCapacity: this.clientNodeCount, vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS, }, securityGroup: props.securityGroup, blockDevices: [{ deviceName: '/dev/xvda', - volume: BlockDeviceVolume.ebs(50, { deleteOnTermination: true, volumeType: props.storageVolumeType }), + volume: BlockDeviceVolume.ebs(50, { deleteOnTermination: true, volumeType: this.storageVolumeType }), }], - init: CloudFormationInit.fromElements(...InfraStack.getCfnInitElement(this, clusterLogGroup, props, 'client')), + init: CloudFormationInit.fromElements(...this.getCfnInitElement(this, clusterLogGroup, 'client')), initOptions: { ignoreFailures: false, }, @@ -327,27 +551,27 @@ export class InfraStack extends Stack { Tags.of(clientNodeAsg).add('role', 'client'); - if (props.mlNodeCount > 0) { + if (this.mlNodeCount > 0) { const mlNodeAsg = new AutoScalingGroup(this, 'mlNodeAsg', { - vpc: props.vpc, - instanceType: props.mlEc2InstanceType, + vpc: props.vpcId, + instanceType: this.mlInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - cpuType: props.cpuType, + cpuType: instanceCpuType, }), role: this.instanceRole, - maxCapacity: props.mlNodeCount, - minCapacity: props.mlNodeCount, - desiredCapacity: props.mlNodeCount, + maxCapacity: this.mlNodeCount, + minCapacity: this.mlNodeCount, + desiredCapacity: this.mlNodeCount, vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS, }, securityGroup: props.securityGroup, blockDevices: [{ deviceName: '/dev/xvda', - volume: BlockDeviceVolume.ebs(props.mlNodeStorage, { deleteOnTermination: true, volumeType: props.storageVolumeType }), + volume: BlockDeviceVolume.ebs(this.mlNodeStorage, { deleteOnTermination: true, volumeType: this.storageVolumeType }), }], - init: CloudFormationInit.fromElements(...InfraStack.getCfnInitElement(this, clusterLogGroup, props, 'ml')), + init: CloudFormationInit.fromElements(...this.getCfnInitElement(this, clusterLogGroup, 'ml')), initOptions: { ignoreFailures: false, }, @@ -363,7 +587,7 @@ export class InfraStack extends Stack { targets: [clientNodeAsg], }); - if (props.dashboardsUrl !== 'undefined') { + if (this.dashboardsUrl !== 'undefined') { // @ts-ignore dashboardsListener.addTargets('dashboardsTarget', { port: 5601, @@ -375,12 +599,12 @@ export class InfraStack extends Stack { value: nlb.loadBalancerDnsName, }); - if (props.enableMonitoring) { - const monitoring = new InfraStackMonitoring(this, props.dashboardsUrl); + if (this.enableMonitoring) { + const monitoring = new InfraStackMonitoring(this, this.dashboardsUrl); } } - private static getCfnInitElement(scope: Stack, logGroup: LogGroup, props: infraProps, nodeType?: string): InitElement[] { + private getCfnInitElement(scope: Stack, logGroup: LogGroup, nodeType?: string): InitElement[] { const configFileDir = join(__dirname, '../opensearch-config'); let opensearchConfig: string; const procstatConfig: ProcstatMetricDefinition[] = [{ @@ -391,7 +615,7 @@ export class InfraStack extends Stack { metrics_collection_interval: 10, }, ]; - if (props.dashboardsUrl !== 'undefined') { + if (this.dashboardsUrl !== 'undefined') { procstatConfig.push({ pattern: 'opensearch-dashboards', measurement: [ @@ -484,7 +708,7 @@ export class InfraStack extends Stack { // eslint-disable-next-line max-len InitCommand.shellCommand('set -ex;/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s'), InitCommand.shellCommand('set -ex; sudo echo "vm.max_map_count=262144" >> /etc/sysctl.conf;sudo sysctl -p'), - InitCommand.shellCommand(`set -ex;mkdir opensearch; curl -L ${props.distributionUrl} -o opensearch.tar.gz;` + InitCommand.shellCommand(`set -ex;mkdir opensearch; curl -L ${this.distributionUrl} -o opensearch.tar.gz;` + 'tar zxf opensearch.tar.gz -C opensearch --strip-components=1; chown -R ec2-user:ec2-user opensearch;', { cwd: '/home/ec2-user', ignoreErrors: false, @@ -493,7 +717,7 @@ export class InfraStack extends Stack { ]; // Add opensearch.yml config - if (props.singleNodeCluster) { + if (this.singleNodeCluster) { const fileContent: any = load(readFileSync(`${configFileDir}/single-node-base-config.yml`, 'utf-8')); fileContent['cluster.name'] = `${scope.stackName}-${scope.account}-${scope.region}`; @@ -526,21 +750,21 @@ export class InfraStack extends Stack { })); } - if (props.distributionUrl.includes('artifacts.opensearch.org') && !props.minDistribution) { + if (this.distributionUrl.includes('artifacts.opensearch.org') && !this.minDistribution) { cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install discovery-ec2 --batch', { cwd: '/home/ec2-user', ignoreErrors: false, })); } else { cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install ' - + `https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${props.opensearchVersion}/latest/linux/${props.cpuArch}` - + `/tar/builds/opensearch/core-plugins/discovery-ec2-${props.opensearchVersion}.zip --batch`, { + + `https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${this.distVersion}/latest/linux/${this.cpuArch}` + + `/tar/builds/opensearch/core-plugins/discovery-ec2-${this.distVersion}.zip --batch`, { cwd: '/home/ec2-user', ignoreErrors: false, })); } - if (props.enableRemoteStore) { + if (this.enableRemoteStore) { // eslint-disable-next-line max-len cfnInitConfig.push(InitCommand.shellCommand(`set -ex;cd opensearch; echo "node.attr.remote_store.segment.repository: ${scope.stackName}-repo" >> config/opensearch.yml`, { cwd: '/home/ec2-user', @@ -573,22 +797,22 @@ export class InfraStack extends Stack { } } - if (props.distributionUrl.includes('artifacts.opensearch.org') && !props.minDistribution) { + if (this.distributionUrl.includes('artifacts.opensearch.org') && !this.minDistribution) { cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install repository-s3 --batch', { cwd: '/home/ec2-user', ignoreErrors: false, })); } else { cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install ' - + `https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${props.opensearchVersion}/latest/linux/${props.cpuArch}` - + `/tar/builds/opensearch/core-plugins/repository-s3-${props.opensearchVersion}.zip --batch`, { + + `https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${this.distVersion}/latest/linux/${this.cpuArch}` + + `/tar/builds/opensearch/core-plugins/repository-s3-${this.distVersion}.zip --batch`, { cwd: '/home/ec2-user', ignoreErrors: false, })); } // add config to disable security if required - if (props.securityDisabled && !props.minDistribution) { + if (this.securityDisabled && !this.minDistribution) { // eslint-disable-next-line max-len cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch; if [ -d "/home/ec2-user/opensearch/plugins/opensearch-security" ]; then echo "plugins.security.disabled: true" >> config/opensearch.yml; fi', { @@ -598,10 +822,8 @@ export class InfraStack extends Stack { } // Check if there are any jvm properties being passed - // @ts-ignore - if (props.jvmSysPropsString.toString() !== 'undefined') { - // @ts-ignore - cfnInitConfig.push(InitCommand.shellCommand(`set -ex; cd opensearch; jvmSysPropsList=$(echo "${props.jvmSysPropsString.toString()}" | tr ',' '\\n');` + if (this.jvmSysProps.toString() !== 'undefined') { + cfnInitConfig.push(InitCommand.shellCommand(`set -ex; cd opensearch; jvmSysPropsList=$(echo "${this.jvmSysProps.toString()}" | tr ',' '\\n');` + 'for sysProp in $jvmSysPropsList;do echo "-D$sysProp" >> config/jvm.options;done', { cwd: '/home/ec2-user', @@ -610,8 +832,7 @@ export class InfraStack extends Stack { } // Check if JVM Heap Memory is set. Default is 1G in the jvm.options file - // @ts-ignore - if (props.use50PercentHeap) { + if (this.use50PercentHeap) { cfnInitConfig.push(InitCommand.shellCommand(`set -ex; cd opensearch; totalMem=\`expr $(free -g | awk '/^Mem:/{print $2}') + 1\`; heapSizeInGb=\`expr $totalMem / 2\`; @@ -623,20 +844,17 @@ export class InfraStack extends Stack { })); } - // @ts-ignore - if (props.additionalConfig.toString() !== 'undefined') { - // @ts-ignore - cfnInitConfig.push(InitCommand.shellCommand(`set -ex; cd opensearch; echo "${props.additionalConfig}">>config/opensearch.yml`, + if (this.additionalConfig.toString() !== 'undefined') { + cfnInitConfig.push(InitCommand.shellCommand(`set -ex; cd opensearch; echo "${this.additionalConfig}">>config/opensearch.yml`, { cwd: '/home/ec2-user', ignoreErrors: false, })); } - if (props.customConfigFiles !== 'undefined') { + if (this.customConfigFiles !== 'undefined') { try { - // @ts-ignore - const jsonObj = JSON.parse(props.customConfigFiles); + const jsonObj = JSON.parse(this.customConfigFiles); Object.keys(jsonObj).forEach((localFileName) => { const getConfig = load(readFileSync(localFileName, 'utf-8')); const remoteConfigLocation = jsonObj[localFileName]; @@ -652,7 +870,7 @@ export class InfraStack extends Stack { } // Starting OpenSearch based on whether the distribution type is min or bundle - if (props.minDistribution) { // using (stackProps.minDistribution) condition is not working when false value is being sent + if (this.minDistribution) { // using (stackProps.minDistribution) condition is not working when false value is being sent cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch; sudo -u ec2-user nohup ./bin/opensearch >> install.log 2>&1 &', { cwd: '/home/ec2-user', @@ -660,7 +878,7 @@ export class InfraStack extends Stack { })); } else { // eslint-disable-next-line max-len - cfnInitConfig.push(InitCommand.shellCommand(`set -ex;cd opensearch; sudo -u ec2-user nohup env OPENSEARCH_INITIAL_ADMIN_PASSWORD=${props.adminPassword} ./opensearch-tar-install.sh >> install.log 2>&1 &`, + cfnInitConfig.push(InitCommand.shellCommand(`set -ex;cd opensearch; sudo -u ec2-user nohup env OPENSEARCH_INITIAL_ADMIN_PASSWORD=${this.adminPassword} ./opensearch-tar-install.sh >> install.log 2>&1 &`, { cwd: '/home/ec2-user', ignoreErrors: false, @@ -668,8 +886,8 @@ export class InfraStack extends Stack { } // If OpenSearch-Dashboards URL is present - if (props.dashboardsUrl !== 'undefined') { - cfnInitConfig.push(InitCommand.shellCommand(`set -ex;mkdir opensearch-dashboards; curl -L ${props.dashboardsUrl} -o opensearch-dashboards.tar.gz;` + if (this.dashboardsUrl !== 'undefined') { + cfnInitConfig.push(InitCommand.shellCommand(`set -ex;mkdir opensearch-dashboards; curl -L ${this.dashboardsUrl} -o opensearch-dashboards.tar.gz;` + 'tar zxf opensearch-dashboards.tar.gz -C opensearch-dashboards --strip-components=1; chown -R ec2-user:ec2-user opensearch-dashboards;', { cwd: '/home/ec2-user', ignoreErrors: false, @@ -681,7 +899,7 @@ export class InfraStack extends Stack { ignoreErrors: false, })); - if (props.securityDisabled && !props.minDistribution) { + if (this.securityDisabled && !this.minDistribution) { cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch-dashboards;' + './bin/opensearch-dashboards-plugin remove securityDashboards --allow-root;' + 'sed -i /^opensearch_security/d config/opensearch_dashboards.yml;' @@ -692,10 +910,8 @@ export class InfraStack extends Stack { })); } - // @ts-ignore - if (props.additionalOsdConfig.toString() !== 'undefined') { - // @ts-ignore - cfnInitConfig.push(InitCommand.shellCommand(`set -ex;cd opensearch-dashboards; echo "${props.additionalOsdConfig}">>config/opensearch_dashboards.yml`, + if (this.additionalOsdConfig.toString() !== 'undefined') { + cfnInitConfig.push(InitCommand.shellCommand(`set -ex;cd opensearch-dashboards; echo "${this.additionalOsdConfig}">>config/opensearch_dashboards.yml`, { cwd: '/home/ec2-user', ignoreErrors: false, diff --git a/lib/infra/remote-store-resources.ts b/lib/infra/remote-store-resources.ts index 7c8a2486301..3d90b82bd35 100644 --- a/lib/infra/remote-store-resources.ts +++ b/lib/infra/remote-store-resources.ts @@ -1,6 +1,6 @@ import { RemovalPolicy, Stack } from 'aws-cdk-lib'; +import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { Bucket } from 'aws-cdk-lib/aws-s3'; -import { Effect, Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; export class RemoteStoreResources { private readonly snapshotS3Bucket: Bucket @@ -11,7 +11,7 @@ export class RemoteStoreResources { this.snapshotS3Bucket = new Bucket(scope, `remote-store-${scope.stackName}`, { removalPolicy: RemovalPolicy.DESTROY, autoDeleteObjects: true, - bucketName: `${scope.stackName}`, + bucketName: `${scope.stackName.toLowerCase()}`, }); this.bucketPolicyStatement = new PolicyStatement({ diff --git a/lib/os-cluster-entrypoint.ts b/lib/os-cluster-entrypoint.ts deleted file mode 100644 index a1dc91930e4..00000000000 --- a/lib/os-cluster-entrypoint.ts +++ /dev/null @@ -1,286 +0,0 @@ -/* Copyright OpenSearch Contributors -SPDX-License-Identifier: Apache-2.0 - -The OpenSearch Contributors require contributions made to -this file be licensed under the Apache-2.0 license or a -compatible open source license. */ - -import { Stack, StackProps } from 'aws-cdk-lib'; -import { EbsDeviceVolumeType } from 'aws-cdk-lib/aws-autoscaling'; -import { - AmazonLinuxCpuType, - IVpc, - InstanceType, - SecurityGroup, -} from 'aws-cdk-lib/aws-ec2'; -import { Construct } from 'constructs'; -import { dump } from 'js-yaml'; -import { InfraStack } from './infra/infra-stack'; -import { NetworkStack } from './networking/vpc-stack'; -import { - arm64Ec2InstanceType, - getArm64InstanceTypes, - getVolumeType, - getX64InstanceTypes, - x64Ec2InstanceType, -} from './opensearch-config/node-config'; - -enum cpuArchEnum{ - X64='x64', - ARM64='arm64' -} - -const getInstanceType = (instanceType: string, arch: string) => { - if (arch === 'x64') { - if (instanceType !== 'undefined') { - return getX64InstanceTypes(instanceType); - } - return getX64InstanceTypes('r5.xlarge'); - } - if (instanceType !== 'undefined') { - return getArm64InstanceTypes(instanceType); - } - return getArm64InstanceTypes('r6g.xlarge'); -}; - -export class OsClusterEntrypoint { - public stacks: Stack[] = []; - - public vpc: IVpc; - - public securityGroup = SecurityGroup; - - constructor(scope: Construct, props: StackProps) { - let instanceCpuType: AmazonLinuxCpuType; - let managerCount: number; - let dataCount: number; - let clientCount: number; - let ingestCount: number; - let mlCount: number; - let infraStackName: string; - let dataNodeStorage: number; - let mlNodeStorage: number; - let ymlConfig: string = 'undefined'; - let osdYmlConfig: string = 'undefined'; - let dataEc2InstanceType: InstanceType; - let mlEc2InstanceType: InstanceType; - let volumeType: EbsDeviceVolumeType; - let customConfigFiles: string = 'undefined'; - - const x64InstanceTypes: string[] = Object.keys(x64Ec2InstanceType); - const arm64InstanceTypes: string[] = Object.keys(arm64Ec2InstanceType); - - const distVersion = `${scope.node.tryGetContext('distVersion')}`; - if (distVersion.toString() === 'undefined') { - throw new Error('Please provide the OS distribution version'); - } - - const securityDisabled = `${scope.node.tryGetContext('securityDisabled')}`; - if (securityDisabled !== 'true' && securityDisabled !== 'false') { - throw new Error('securityEnabled parameter is required to be set as - true or false'); - } - const insecure = securityDisabled === 'true'; - - const adminPassword: String = insecure ? '' : `${scope.node.tryGetContext('adminPassword')}`; - if (!insecure && Number.parseFloat(distVersion) >= 2.12 && adminPassword === 'undefined') { - throw new Error('adminPassword parameter is required to be set when security is enabled'); - } - - const minDistribution = `${scope.node.tryGetContext('minDistribution')}`; - if (minDistribution !== 'true' && minDistribution !== 'false') { - throw new Error('minDistribution parameter is required to be set as - true or false'); - } - const minDist = minDistribution === 'true'; - - const distributionUrl = `${scope.node.tryGetContext('distributionUrl')}`; - if (distributionUrl.toString() === 'undefined') { - throw new Error('distributionUrl parameter is required. Please provide the artifact url to download'); - } - - const dashboardUrl = `${scope.node.tryGetContext('dashboardsUrl')}`; - - const cpuArch = `${scope.node.tryGetContext('cpuArch')}`; - - const dataInstanceType = `${scope.node.tryGetContext('dataInstanceType')}`; - const mlInstanceType = `${scope.node.tryGetContext('mlInstanceType')}`; - - if (cpuArch.toString() === 'undefined') { - throw new Error('cpuArch parameter is required. The provided value should be either x64 or arm64, any other value is invalid'); - // @ts-ignore - } else if (Object.values(cpuArchEnum).includes(cpuArch.toString())) { - if (cpuArch.toString() === cpuArchEnum.X64) { - instanceCpuType = AmazonLinuxCpuType.X86_64; - dataEc2InstanceType = getInstanceType(dataInstanceType, cpuArch.toString()); - mlEc2InstanceType = getInstanceType(mlInstanceType, cpuArch.toString()); - } else { - instanceCpuType = AmazonLinuxCpuType.ARM_64; - dataEc2InstanceType = getInstanceType(dataInstanceType, cpuArch.toString()); - mlEc2InstanceType = getInstanceType(mlInstanceType, cpuArch.toString()); - } - } else { - throw new Error('Please provide a valid cpu architecture. The valid value can be either x64 or arm64'); - } - - const singleNodeCluster = `${scope.node.tryGetContext('singleNodeCluster')}`; - const isSingleNode = singleNodeCluster === 'true'; - - const managerNodeCount = `${scope.node.tryGetContext('managerNodeCount')}`; - if (managerNodeCount.toString() === 'undefined') { - managerCount = 3; - } else { - managerCount = parseInt(managerNodeCount, 10); - } - - const dataNodeCount = `${scope.node.tryGetContext('dataNodeCount')}`; - if (dataNodeCount.toString() === 'undefined') { - dataCount = 2; - } else { - dataCount = parseInt(dataNodeCount, 10); - } - - const clientNodeCount = `${scope.node.tryGetContext('clientNodeCount')}`; - if (clientNodeCount.toString() === 'undefined') { - clientCount = 0; - } else { - clientCount = parseInt(clientNodeCount, 10); - } - - const ingestNodeCount = `${scope.node.tryGetContext('ingestNodeCount')}`; - if (ingestNodeCount.toString() === 'undefined') { - ingestCount = 0; - } else { - ingestCount = parseInt(clientNodeCount, 10); - } - - const mlNodeCount = `${scope.node.tryGetContext('mlNodeCount')}`; - if (mlNodeCount.toString() === 'undefined') { - mlCount = 0; - } else { - mlCount = parseInt(mlNodeCount, 10); - } - - const dataSize = `${scope.node.tryGetContext('dataNodeStorage')}`; - if (dataSize === 'undefined') { - dataNodeStorage = 100; - } else { - dataNodeStorage = parseInt(dataSize, 10); - } - - const inputVolumeType = `${scope.node.tryGetContext('storageVolumeType')}`; - if (inputVolumeType.toString() === 'undefined') { - // use gp2 volume by default - volumeType = getVolumeType('gp2'); - } else { - volumeType = getVolumeType(inputVolumeType); - } - - const mlSize = `${scope.node.tryGetContext('mlNodeStorage')}`; - if (mlSize === 'undefined') { - mlNodeStorage = 100; - } else { - mlNodeStorage = parseInt(mlSize, 10); - } - - const jvmSysProps = `${scope.node.tryGetContext('jvmSysProps')}`; - - const osConfig = `${scope.node.tryGetContext('additionalConfig')}`; - if (osConfig.toString() !== 'undefined') { - try { - const jsonObj = JSON.parse(osConfig); - ymlConfig = dump(jsonObj); - } catch (e) { - throw new Error(`Encountered following error while parsing additionalConfig json parameter: ${e}`); - } - } - - const osdConfig = `${scope.node.tryGetContext('additionalOsdConfig')}`; - if (osdConfig.toString() !== 'undefined') { - try { - const jsonObj = JSON.parse(osdConfig); - osdYmlConfig = dump(jsonObj); - } catch (e) { - throw new Error(`Encountered following error while parsing additionalOsdConfig json parameter: ${e}`); - } - } - - customConfigFiles = `${scope.node.tryGetContext('customConfigFiles')}`; - - const suffix = `${scope.node.tryGetContext('suffix')}`; - const networkStackSuffix = `${scope.node.tryGetContext('networkStackSuffix')}`; - - const use50heap = `${scope.node.tryGetContext('use50PercentHeap')}`; - const use50PercentHeap = use50heap === 'true'; - - const nlbScheme = `${scope.node.tryGetContext('isInternal')}`; - const isInternal = nlbScheme === 'true'; - - const remoteStore = `${scope.node.tryGetContext('enableRemoteStore')}`; - const enableRemoteStore = remoteStore === 'true'; - - const monitoringAndAlarms = `${scope.node.tryGetContext('enableMonitoring')}`; - const enableMonitoring = monitoringAndAlarms === 'true'; - - const customRoleArn = `${scope.node.tryGetContext('customRoleArn')}`; - - let networkStackName = 'opensearch-network-stack'; - if (networkStackSuffix !== 'undefined') { - networkStackName = `opensearch-network-stack-${networkStackSuffix}`; - } - - const network = new NetworkStack(scope, networkStackName, { - ...props, - }); - - this.vpc = network.vpc; - // @ts-ignore - this.securityGroup = network.osSecurityGroup; - - this.stacks.push(network); - - if (suffix === 'undefined') { - infraStackName = 'opensearch-infra-stack'; - } else { - infraStackName = `opensearch-infra-stack-${suffix}`; - } - - // @ts-ignore - const infraStack = new InfraStack(scope, infraStackName, { - vpc: this.vpc, - securityDisabled: insecure, - adminPassword, - opensearchVersion: distVersion, - clientNodeCount: clientCount, - cpuArch, - cpuType: instanceCpuType, - dataEc2InstanceType, - mlEc2InstanceType, - dashboardsUrl: dashboardUrl, - dataNodeCount: dataCount, - distributionUrl, - ingestNodeCount: ingestCount, - managerNodeCount: managerCount, - minDistribution: minDist, - mlNodeCount: mlCount, - // @ts-ignore - securityGroup: this.securityGroup, - singleNodeCluster: isSingleNode, - dataNodeStorage, - mlNodeStorage, - use50PercentHeap, - isInternal, - enableRemoteStore, - storageVolumeType: volumeType, - customRoleArn, - jvmSysPropsString: jvmSysProps, - additionalConfig: ymlConfig, - additionalOsdConfig: osdYmlConfig, - customConfigFiles, - enableMonitoring, - ...props, - }); - - infraStack.addDependency(network); - - this.stacks.push(infraStack); - } -} diff --git a/test/os-cluster.test.ts b/test/opensearch-cluster-cdk.test.ts similarity index 69% rename from test/os-cluster.test.ts rename to test/opensearch-cluster-cdk.test.ts index 20a80456365..f170478240d 100644 --- a/test/os-cluster.test.ts +++ b/test/opensearch-cluster-cdk.test.ts @@ -7,11 +7,11 @@ compatible open source license. */ import { App } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; -import { OsClusterEntrypoint } from '../lib/os-cluster-entrypoint'; +import { InfraStack } from '../lib/infra/infra-stack'; +import { NetworkStack } from '../lib/networking/vpc-stack'; test('Test Resources with security disabled multi-node default instance types', () => { const app = new App({ - context: { securityDisabled: true, minDistribution: false, @@ -31,18 +31,99 @@ test('Test Resources with security disabled multi-node default instance types', }); // WHEN - const securityDisabledStack = new OsClusterEntrypoint(app, { + const netStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: netStack.vpc, + securityGroup: netStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); // THEN - expect(securityDisabledStack.stacks).toHaveLength(2); - const networkStack = securityDisabledStack.stacks.filter((s) => s.stackName === 'opensearch-network-stack')[0]; - const networkTemplate = Template.fromStack(networkStack); + const networkTemplate = Template.fromStack(netStack); + networkTemplate.resourceCountIs('AWS::EC2::VPC', 1); + networkTemplate.resourceCountIs('AWS::EC2::SecurityGroup', 1); + + const infraTemplate = Template.fromStack(infraStack); + infraTemplate.resourceCountIs('AWS::Logs::LogGroup', 1); + infraTemplate.resourceCountIs('AWS::IAM::Role', 1); + infraTemplate.resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 3); + infraTemplate.resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + infraTemplate.resourceCountIs('AWS::ElasticLoadBalancingV2::Listener', 2); + infraTemplate.resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 2); + infraTemplate.resourceCountIs('AWS::AutoScaling::LaunchConfiguration', 3); + infraTemplate.resourceCountIs('AWS::CloudWatch::Alarm', 4); + infraTemplate.resourceCountIs('AWS::CloudWatch::Dashboard', 1); + infraTemplate.hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + Port: 80, + Protocol: 'TCP', + }); + infraTemplate.hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + Port: 8443, + Protocol: 'TCP', + }); + infraTemplate.hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + InstanceType: 'r5.xlarge', + IamInstanceProfile: { + Ref: 'dataNodeAsgInstanceProfileEC27E8D1', + }, + }); + infraTemplate.hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + InstanceType: 'c5.xlarge', + IamInstanceProfile: { + Ref: 'seedNodeAsgInstanceProfile6F1EA4FF', + }, + }); + infraTemplate.hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + InstanceType: 'c5.xlarge', + IamInstanceProfile: { + Ref: 'managerNodeAsgInstanceProfile1415C2CF', + }, + }); + infraTemplate.hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + MetadataOptions: { + HttpTokens: 'required', + }, + }); +}); + +test('Test Resources with security disabled multi-node default instance types using class properties', () => { + const app = new App({}); + + // WHEN + const netStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: netStack.vpc, + securityGroup: netStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + securityDisabled: true, + minDistribution: false, + distributionUrl: 'www.example.com', + cpuArch: 'x64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + distVersion: '1.0.0', + additionalConfig: '{ "name": "John Doe", "age": 30, "email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", "something_else.enabled": "false" }', + // eslint-disable-next-line max-len + customConfigFiles: '{"test/data/config.yml": "opensearch/config/opensearch-security/config.yml", "test/data/roles.yml": "opensearch/config/opensearch-security/roles.yml"}', + enableMonitoring: true, + }); + + // THEN + const networkTemplate = Template.fromStack(netStack); networkTemplate.resourceCountIs('AWS::EC2::VPC', 1); networkTemplate.resourceCountIs('AWS::EC2::SecurityGroup', 1); - const infraStack = securityDisabledStack.stacks.filter((s) => s.stackName === 'opensearch-infra-stack')[0]; const infraTemplate = Template.fromStack(infraStack); infraTemplate.resourceCountIs('AWS::Logs::LogGroup', 1); infraTemplate.resourceCountIs('AWS::IAM::Role', 1); @@ -107,11 +188,17 @@ test('Test Resources with security enabled multi-node with existing Vpc with use }); // WHEN - const securityEnabledStack = new OsClusterEntrypoint(app, { + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { env: { account: 'test-account', region: 'us-east-1' }, }); - expect(securityEnabledStack.stacks).toHaveLength(2); - const networkStack = securityEnabledStack.stacks.filter((s) => s.stackName === 'opensearch-network-stack')[0]; + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); + const networkTemplate = Template.fromStack(networkStack); networkTemplate.resourceCountIs('AWS::EC2::VPC', 0); networkTemplate.resourceCountIs('AWS::EC2::SecurityGroup', 1); @@ -126,7 +213,6 @@ test('Test Resources with security enabled multi-node with existing Vpc with use ], }); - const infraStack = securityEnabledStack.stacks.filter((s) => s.stackName === 'opensearch-infra-stack')[0]; const infraTemplate = Template.fromStack(infraStack); infraTemplate.resourceCountIs('AWS::AutoScaling::LaunchConfiguration', 4); infraTemplate.hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { @@ -184,16 +270,20 @@ test('Test Resources with security enabled single-node cluster', () => { }); // WHEN - const singleNodeStack = new OsClusterEntrypoint(app, { + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); - expect(singleNodeStack.stacks).toHaveLength(2); - const networkStack = singleNodeStack.stacks.filter((s) => s.stackName === 'opensearch-network-stack')[0]; const networkTemplate = Template.fromStack(networkStack); networkTemplate.resourceCountIs('AWS::EC2::VPC', 1); networkTemplate.resourceCountIs('AWS::EC2::SecurityGroup', 1); - const infraStack = singleNodeStack.stacks.filter((s) => s.stackName === 'opensearch-infra-stack')[0]; const infraTemplate = Template.fromStack(infraStack); infraTemplate.hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { Port: 443, @@ -240,7 +330,15 @@ test('Throw error on wrong cpu arch to instance mapping', () => { }); // WHEN try { - const testStack = new OsClusterEntrypoint(app, { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -248,8 +346,10 @@ test('Throw error on wrong cpu arch to instance mapping', () => { fail('Expected an error to be thrown'); } catch (error) { expect(error).toBeInstanceOf(Error); - // eslint-disable-next-line max-len - expect(error.message).toEqual('Invalid instance type provided, please provide any one the following: m6g.xlarge,m6g.2xlarge,c6g.large,c6g.xlarge,r6g.large,r6g.xlarge,r6g.2xlarge,r6g.4xlarge,r6g.8xlarge,g5g.large,g5g.xlarge'); + // @ts-ignore + expect(error.message).toEqual('Invalid instance type provided, please provide any one the following: ' + + 'm6g.xlarge,m6g.2xlarge,c6g.large,c6g.xlarge,r6g.large,r6g.xlarge,r6g.2xlarge,r6g.4xlarge,r6g.8xlarge,' + + 'g5g.large,g5g.xlarge'); } }); @@ -272,7 +372,15 @@ test('Throw error on ec2 instance outside of enum list', () => { }); // WHEN try { - const testStack = new OsClusterEntrypoint(app, { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -280,8 +388,10 @@ test('Throw error on ec2 instance outside of enum list', () => { fail('Expected an error to be thrown'); } catch (error) { expect(error).toBeInstanceOf(Error); - // eslint-disable-next-line max-len - expect(error.message).toEqual('Invalid instance type provided, please provide any one the following: m5.xlarge,m5.2xlarge,c5.large,c5.xlarge,r5.large,r5.xlarge,r5.2xlarge,r5.4xlarge,r5.8xlarge,g5.large,g5.xlarge,i3.large,i3.xlarge,i3.2xlarge,i3.4xlarge,i3.8xlarge,inf1.xlarge,inf1.2xlarge'); + // @ts-ignore + expect(error.message).toEqual('Invalid instance type provided, please provide any one the following: ' + + 'm5.xlarge,m5.2xlarge,c5.large,c5.xlarge,r5.large,r5.xlarge,r5.2xlarge,r5.4xlarge,r5.8xlarge,g5.large,' + + 'g5.xlarge,i3.large,i3.xlarge,i3.2xlarge,i3.4xlarge,i3.8xlarge,inf1.xlarge,inf1.2xlarge'); } }); @@ -304,14 +414,17 @@ test('Test multi-node cluster with only data-nodes', () => { }); // WHEN - const testStack = new OsClusterEntrypoint(app, { + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { env: { account: 'test-account', region: 'us-east-1' }, }); - // THEN - expect(testStack.stacks).toHaveLength(2); + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); - const infraStack = testStack.stacks.filter((s) => s.stackName === 'opensearch-infra-stack')[0]; const infraTemplate = Template.fromStack(infraStack); infraTemplate.resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 2); infraTemplate.resourceCountIs('AWS::AutoScaling::LaunchConfiguration', 2); @@ -356,14 +469,16 @@ test('Test multi-node cluster with remote-store enabled', () => { }); // WHEN - const testStack = new OsClusterEntrypoint(app, { + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { env: { account: 'test-account', region: 'us-east-1' }, }); - // THEN - expect(testStack.stacks).toHaveLength(2); - - const infraStack = testStack.stacks.filter((s) => s.stackName === 'opensearch-infra-stack')[0]; + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); const infraTemplate = Template.fromStack(infraStack); infraTemplate.resourceCountIs('AWS::S3::Bucket', 1); infraTemplate.resourceCountIs('AWS::S3::BucketPolicy', 1); @@ -447,7 +562,14 @@ test('Throw error on unsupported ebs volume type', () => { }); // WHEN try { - const testStack = new OsClusterEntrypoint(app, { + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -455,8 +577,9 @@ test('Throw error on unsupported ebs volume type', () => { fail('Expected an error to be thrown'); } catch (error) { expect(error).toBeInstanceOf(Error); - // eslint-disable-next-line max-len - expect(error.message).toEqual('Invalid volume type provided, please provide any one of the following: standard, gp2, gp3'); + // @ts-ignore + expect(error.message).toEqual('Invalid volume type provided, please provide any one of the following: ' + + 'standard, gp2, gp3'); } }); @@ -480,14 +603,18 @@ test('Test multi-node cluster with custom IAM Role', () => { }); // WHEN - const testStack = new OsClusterEntrypoint(app, { + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { env: { account: 'test-account', region: 'us-east-1' }, }); - // THEN - expect(testStack.stacks).toHaveLength(2); + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); - const infraStack = testStack.stacks.filter((s) => s.stackName === 'opensearch-infra-stack')[0]; + // THEN const infraTemplate = Template.fromStack(infraStack); infraTemplate.resourceCountIs('AWS::IAM::Role', 0); infraTemplate.hasResourceProperties('AWS::IAM::InstanceProfile', { @@ -515,7 +642,15 @@ test('Throw error on incorrect JSON', () => { }); // WHEN try { - const testStack = new OsClusterEntrypoint(app, { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -523,8 +658,9 @@ test('Throw error on incorrect JSON', () => { fail('Expected an error to be thrown'); } catch (error) { expect(error).toBeInstanceOf(Error); - // eslint-disable-next-line max-len - expect(error.message).toEqual('Encountered following error while parsing customConfigFiles json parameter: SyntaxError: Unexpected token o in JSON at position 25'); + // @ts-ignore + expect(error.message).toEqual('Encountered following error while parsing customConfigFiles json parameter: ' + + 'SyntaxError: Unexpected token o in JSON at position 25'); } }); @@ -548,7 +684,14 @@ test('Throw error when security is enabled and adminPassword is not defined and }); try { - const testStack = new OsClusterEntrypoint(app, { + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpcId: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -556,36 +699,7 @@ test('Throw error when security is enabled and adminPassword is not defined and fail('Expected an error to be thrown'); } catch (error) { expect(error).toBeInstanceOf(Error); - // eslint-disable-next-line max-len + // @ts-ignore expect(error.message).toEqual('adminPassword parameter is required to be set when security is enabled'); } }); - -test('Should not throw error when security is enabled and adminPassword is defined and dist version is greater than or equal to 2.12', () => { - const app = new App({ - context: { - securityDisabled: false, - adminPassword: 'Admin_1234', - minDistribution: false, - distributionUrl: 'www.example.com', - cpuArch: 'x64', - singleNodeCluster: false, - dashboardsUrl: 'www.example.com', - distVersion: '2.12.0', - serverAccessType: 'ipv4', - restrictServerAccessTo: 'all', - managerNodeCount: 0, - dataNodeCount: 3, - dataNodeStorage: 200, - customRoleArn: 'arn:aws:iam::12345678:role/customRoleName', - }, - }); - - // WHEN - const testStack = new OsClusterEntrypoint(app, { - env: { account: 'test-account', region: 'us-east-1' }, - }); - - // THEN - expect(testStack.stacks).toHaveLength(2); -}); From d8ab3f0760cb6753e5a4273f210b640e5740fbb9 Mon Sep 17 00:00:00 2001 From: Sayali Gaikawad Date: Fri, 22 Dec 2023 16:17:13 -0800 Subject: [PATCH 2/5] Add comments Signed-off-by: Sayali Gaikawad --- bin/app.ts | 2 +- lib/infra/infra-stack.ts | 45 ++++++++++++++++++++++++----- test/opensearch-cluster-cdk.test.ts | 24 +++++++-------- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/bin/app.ts b/bin/app.ts index f0109085be1..6b0d9b0f9f5 100644 --- a/bin/app.ts +++ b/bin/app.ts @@ -33,7 +33,7 @@ const networkStack = new NetworkStack(app, networkStackName, { // @ts-ignore const infraStack = new InfraStack(app, infraStackName, { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account, region }, }); diff --git a/lib/infra/infra-stack.ts b/lib/infra/infra-stack.ts index 870f1cb324a..0884ac5be52 100644 --- a/lib/infra/infra-stack.ts +++ b/lib/infra/infra-stack.ts @@ -65,34 +65,63 @@ const getInstanceType = (instanceType: string, arch: string) => { }; export interface InfraProps extends StackProps { - readonly vpcId: IVpc, + /** VPC used for deploying all resources */ + readonly vpc: IVpc, + /** Security group required for all resources */ readonly securityGroup: ISecurityGroup, + /** OpenSearch Distribution version */ readonly distVersion?: string, + /** CPU architecture to deploy all EC2 */ readonly cpuArch?: string, + /** Security enabled or disabled for the cluster */ readonly securityDisabled?: boolean, + /** Admin password for your cluster */ readonly adminPassword?: string, + /** Whether it is a min distribution */ readonly minDistribution?: boolean, + /** URL to download OpenSearch distribution from */ readonly distributionUrl?: string, + /** URL to download opensearch dashboards distribution from */ readonly dashboardsUrl?: string, + /** Whether it is a single node cluster */ readonly singleNodeCluster?: boolean, + /** Number of manager nodes */ readonly managerNodeCount?: number, + /** Number of data nodes */ readonly dataNodeCount?: number, + /** Number of ingest nodes */ readonly ingestNodeCount?: number, + /** Number of client nodes */ readonly clientNodeCount?: number, + /** Number of ml modes */ readonly mlNodeCount?: number, + /** EBS block storage size for data nodes */ readonly dataNodeStorage?: number, + /** EBS block storage size for ml nodes */ readonly mlNodeStorage?: number, + /** EC2 instance type for data nodes */ readonly dataInstanceType?: InstanceType, + /** EC2 instance type for ML nodes */ readonly mlInstanceType?: InstanceType, + /** Whether to use 50% heap */ readonly use50PercentHeap?: boolean, + /** Whether the cluster should be internal only */ readonly isInternal?: boolean, + /** Whether to enable remote store feature */ readonly enableRemoteStore?: boolean, + /** EBS volume type for all nodes */ readonly storageVolumeType?: EbsDeviceVolumeType, + /** Custom role to use as EC2 instance profile */ readonly customRoleArn?: string, + /** JVM system properties */ readonly jvmSysProps?: string, + /** Any additional config to add to opensearch.yml */ readonly additionalConfig?: string, + /** Any additional config to add to opensearch-dashboards.yml */ readonly additionalOsdConfig?: string, + /** Add any custom configuration files to the cluster */ readonly customConfigFiles?: string, + /** Whether to enable monioring with alarms */ readonly enableMonitoring?: boolean, } @@ -352,7 +381,7 @@ export class InfraStack extends Stack { ? InstanceType.of(InstanceClass.C5, InstanceSize.XLARGE) : InstanceType.of(InstanceClass.C6G, InstanceSize.XLARGE); const nlb = new NetworkLoadBalancer(this, 'clusterNlb', { - vpc: props.vpcId, + vpc: props.vpc, internetFacing: (!this.isInternal), crossZoneEnabled: true, }); @@ -379,7 +408,7 @@ export class InfraStack extends Stack { if (this.singleNodeCluster) { console.log('Single node value is true, creating single node configurations'); singleNodeInstance = new Instance(this, 'single-node-instance', { - vpc: props.vpcId, + vpc: props.vpc, instanceType: singleNodeInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, @@ -428,7 +457,7 @@ export class InfraStack extends Stack { if (managerAsgCapacity > 0) { const managerNodeAsg = new AutoScalingGroup(this, 'managerNodeAsg', { - vpc: props.vpcId, + vpc: props.vpc, instanceType: defaultInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, @@ -461,7 +490,7 @@ export class InfraStack extends Stack { } const seedNodeAsg = new AutoScalingGroup(this, 'seedNodeAsg', { - vpc: props.vpcId, + vpc: props.vpc, instanceType: (seedConfig === 'seed-manager') ? defaultInstanceType : this.dataInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, @@ -490,7 +519,7 @@ export class InfraStack extends Stack { Tags.of(seedNodeAsg).add('role', 'manager'); const dataNodeAsg = new AutoScalingGroup(this, 'dataNodeAsg', { - vpc: props.vpcId, + vpc: props.vpc, instanceType: this.dataInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, @@ -521,7 +550,7 @@ export class InfraStack extends Stack { clientNodeAsg = dataNodeAsg; } else { clientNodeAsg = new AutoScalingGroup(this, 'clientNodeAsg', { - vpc: props.vpcId, + vpc: props.vpc, instanceType: defaultInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, @@ -553,7 +582,7 @@ export class InfraStack extends Stack { if (this.mlNodeCount > 0) { const mlNodeAsg = new AutoScalingGroup(this, 'mlNodeAsg', { - vpc: props.vpcId, + vpc: props.vpc, instanceType: this.mlInstanceType, machineImage: MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, diff --git a/test/opensearch-cluster-cdk.test.ts b/test/opensearch-cluster-cdk.test.ts index f170478240d..7b3f98a1dc9 100644 --- a/test/opensearch-cluster-cdk.test.ts +++ b/test/opensearch-cluster-cdk.test.ts @@ -37,7 +37,7 @@ test('Test Resources with security disabled multi-node default instance types', // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: netStack.vpc, + vpc: netStack.vpc, securityGroup: netStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -102,7 +102,7 @@ test('Test Resources with security disabled multi-node default instance types us // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: netStack.vpc, + vpc: netStack.vpc, securityGroup: netStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, securityDisabled: true, @@ -194,7 +194,7 @@ test('Test Resources with security enabled multi-node with existing Vpc with use // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -276,7 +276,7 @@ test('Test Resources with security enabled single-node cluster', () => { // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -337,7 +337,7 @@ test('Throw error on wrong cpu arch to instance mapping', () => { // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -379,7 +379,7 @@ test('Throw error on ec2 instance outside of enum list', () => { // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -420,7 +420,7 @@ test('Test multi-node cluster with only data-nodes', () => { // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -475,7 +475,7 @@ test('Test multi-node cluster with remote-store enabled', () => { // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -568,7 +568,7 @@ test('Throw error on unsupported ebs volume type', () => { // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -609,7 +609,7 @@ test('Test multi-node cluster with custom IAM Role', () => { // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -649,7 +649,7 @@ test('Throw error on incorrect JSON', () => { // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); @@ -690,7 +690,7 @@ test('Throw error when security is enabled and adminPassword is not defined and // @ts-ignore const infraStack = new InfraStack(app, 'opensearch-infra-stack', { - vpcId: networkStack.vpc, + vpc: networkStack.vpc, securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); From 251c0f2035e01fbc9abeb09a382c9bfae07f070e Mon Sep 17 00:00:00 2001 From: Sayali Gaikawad Date: Fri, 22 Dec 2023 17:55:31 -0800 Subject: [PATCH 3/5] Add the test back Signed-off-by: Sayali Gaikawad --- test/opensearch-cluster-cdk.test.ts | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/opensearch-cluster-cdk.test.ts b/test/opensearch-cluster-cdk.test.ts index 7b3f98a1dc9..480b9c423cf 100644 --- a/test/opensearch-cluster-cdk.test.ts +++ b/test/opensearch-cluster-cdk.test.ts @@ -703,3 +703,36 @@ test('Throw error when security is enabled and adminPassword is not defined and expect(error.message).toEqual('adminPassword parameter is required to be set when security is enabled'); } }); + +test('Should not throw error when security is enabled and adminPassword is defined and dist version is greater than or equal to 2.12', () => { + const app = new App({ + context: { + securityDisabled: false, + adminPassword: 'Admin_1234', + minDistribution: false, + distributionUrl: 'www.example.com', + cpuArch: 'x64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + distVersion: '2.12.0', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + managerNodeCount: 0, + dataNodeCount: 3, + dataNodeStorage: 200, + customRoleArn: 'arn:aws:iam::12345678:role/customRoleName', + }, + }); + + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); +}); \ No newline at end of file From f33fd3cf943642df022511d78d5326f0dd717caf Mon Sep 17 00:00:00 2001 From: Sayali Gaikawad Date: Thu, 4 Jan 2024 11:50:45 -0800 Subject: [PATCH 4/5] Add more tests Signed-off-by: Sayali Gaikawad --- test/infra-stack-props.test.ts | 281 ++++++++++++++++++++++++++++ test/opensearch-cluster-cdk.test.ts | 2 +- 2 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 test/infra-stack-props.test.ts diff --git a/test/infra-stack-props.test.ts b/test/infra-stack-props.test.ts new file mode 100644 index 00000000000..5c08307bad5 --- /dev/null +++ b/test/infra-stack-props.test.ts @@ -0,0 +1,281 @@ +/* Copyright OpenSearch Contributors +SPDX-License-Identifier: Apache-2.0 + +The OpenSearch Contributors require contributions made to +this file be licensed under the Apache-2.0 license or a +compatible open source license. */ + +import { App } from 'aws-cdk-lib'; +import { InfraStack } from '../lib/infra/infra-stack'; +import { NetworkStack } from '../lib/networking/vpc-stack'; + +test('Throw error on incorrect JSON for opensearch', () => { + const app = new App({ + context: { + securityDisabled: true, + minDistribution: false, + distributionUrl: 'www.example.com', + cpuArch: 'x64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + distVersion: '1.0.0', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + additionalConfig: '{ "name": "John Doe", "age": 30, email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", "something_else.enabled": "false" }', + }, + }); + // WHEN + try { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // eslint-disable-next-line no-undef + fail('Expected an error to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + // @ts-ignore + expect(error.message).toEqual('Encountered following error while parsing additionalConfig json parameter: ' + + 'SyntaxError: Unexpected token e in JSON at position 33'); + } +}); + +test('Throw error on incorrect JSON for opensearch-dashboards', () => { + const app = new App({ + context: { + securityDisabled: true, + minDistribution: false, + distributionUrl: 'www.example.com', + cpuArch: 'x64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + distVersion: '1.0.0', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + additionalConfig: '{ "name": "John Doe", "age": 30, "email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", something_else.enabled": "false" }', + }, + }); + // WHEN + try { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // eslint-disable-next-line no-undef + fail('Expected an error to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + // @ts-ignore + expect(error.message).toEqual('Encountered following error while parsing additionalOsdConfig json parameter: ' + + 'SyntaxError: Unexpected token s in JSON at position 31'); + } +}); + +test('Throw error on missing distVersion', () => { + const app = new App({ + context: { + securityDisabled: true, + minDistribution: false, + distributionUrl: 'www.example.com', + cpuArch: 'x64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + additionalConfig: '{ "name": "John Doe", "age": 30, "email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", something_else.enabled": "false" }', + }, + }); + // WHEN + try { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // eslint-disable-next-line no-undef + fail('Expected an error to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + // @ts-ignore + expect(error.message).toEqual('distVersion parameter cannot be empty! Please provide the OpenSearch distribution version'); + } +}); + +test('Throw error on missing security parameter', () => { + const app = new App({ + context: { + distVersion: '1.0.0', + minDistribution: false, + distributionUrl: 'www.example.com', + cpuArch: 'x64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + additionalConfig: '{ "name": "John Doe", "age": 30, "email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", something_else.enabled": "false" }', + }, + }); + // WHEN + try { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // eslint-disable-next-line no-undef + fail('Expected an error to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + // @ts-ignore + expect(error.message).toEqual('securityDisabled parameter is required to be set as - true or false'); + } +}); + +test('Throw error on missing some parameter', () => { + const app = new App({ + context: { + distVersion: '1.0.0', + distributionUrl: 'www.example.com', + cpuArch: 'x64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + additionalConfig: '{ "name": "John Doe", "age": 30, "email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", "something_else.enabled": "false" }', + }, + }); + // WHEN + try { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + securityDisabled: false, + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // eslint-disable-next-line no-undef + fail('Expected an error to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + // @ts-ignore + expect(error.message).toEqual('minDistribution parameter is required to be set as - true or false'); + } +}); + +test('Throw error on invalid CPU Arch', () => { + const app = new App({ + context: { + distVersion: '1.0.0', + securityDisabled: false, + minDistribution: false, + distributionUrl: 'www.example.com', + cpuArch: 'someRandomArch', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + additionalConfig: '{ "name": "John Doe", "age": 30, "email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", something_else.enabled": "false" }', + }, + }); + // WHEN + try { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // eslint-disable-next-line no-undef + fail('Expected an error to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + // @ts-ignore + expect(error.message).toEqual('Please provide a valid cpu architecture. The valid value can be either x64 or arm64'); + } +}); + +test('Throw error on missing CPU Arch', () => { + const app = new App({ + context: { + distVersion: '1.0.0', + securityDisabled: false, + minDistribution: false, + distributionUrl: 'www.example.com', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + additionalConfig: '{ "name": "John Doe", "age": 30, "email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", something_else.enabled": "false" }', + }, + }); + // WHEN + try { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // eslint-disable-next-line no-undef + fail('Expected an error to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + // @ts-ignore + expect(error.message).toEqual('cpuArch parameter is required. Valid inputs: x64 or arm64'); + } +}); diff --git a/test/opensearch-cluster-cdk.test.ts b/test/opensearch-cluster-cdk.test.ts index 480b9c423cf..9f5a2a7f4f3 100644 --- a/test/opensearch-cluster-cdk.test.ts +++ b/test/opensearch-cluster-cdk.test.ts @@ -735,4 +735,4 @@ test('Should not throw error when security is enabled and adminPassword is defi securityGroup: networkStack.osSecurityGroup, env: { account: 'test-account', region: 'us-east-1' }, }); -}); \ No newline at end of file +}); From 6b9e5ec98df5123745ff5aff75641a4a848671de Mon Sep 17 00:00:00 2001 From: Sayali Gaikawad Date: Thu, 4 Jan 2024 11:56:58 -0800 Subject: [PATCH 5/5] Add more tests Signed-off-by: Sayali Gaikawad --- test/infra-stack-props.test.ts | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/infra-stack-props.test.ts b/test/infra-stack-props.test.ts index 5c08307bad5..f8be7563e37 100644 --- a/test/infra-stack-props.test.ts +++ b/test/infra-stack-props.test.ts @@ -279,3 +279,41 @@ test('Throw error on missing CPU Arch', () => { expect(error.message).toEqual('cpuArch parameter is required. Valid inputs: x64 or arm64'); } }); + +test('Throw error on invalid CPU Arch', () => { + const app = new App({ + context: { + distVersion: '1.0.0', + securityDisabled: false, + minDistribution: false, + cpuArch: 'arm64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + additionalConfig: '{ "name": "John Doe", "age": 30, "email": "johndoe@example.com" }', + additionalOsdConfig: '{ "something.enabled": "true", something_else.enabled": "false" }', + }, + }); + // WHEN + try { + // WHEN + const networkStack = new NetworkStack(app, 'opensearch-network-stack', { + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // @ts-ignore + const infraStack = new InfraStack(app, 'opensearch-infra-stack', { + vpc: networkStack.vpc, + securityGroup: networkStack.osSecurityGroup, + env: { account: 'test-account', region: 'us-east-1' }, + }); + + // eslint-disable-next-line no-undef + fail('Expected an error to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + // @ts-ignore + expect(error.message).toEqual('distributionUrl parameter is required. Please provide the OpenSearch distribution artifact url to download'); + } +});