AWSTemplateFormatVersion: "2010-09-09" # MIT License # # Copyright (c) 2021 Qumulo, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. Description: This is the main template to spin up a Qumulo Cluster with all WAF supporting infrastructure. It calls subordinate CloudFormation templates to instantiate the infrastructure. (qs-1s6n2i6af) Metadata: cfn-lint: config: ignore_checks: - W2001 - W9002 - W9003 - W9004 - W9006 - E9010 Parameters: QSS3BucketName: Type: String QSS3BucketRegion: Type: String QSS3KeyPrefix: Type: String KeyPair: Type: String EnvType: Type: String VPCId: Type: String QPublicMgmt: Type: String QPublicRepl: Type: String NumberAZs: Type: String PublicSubnetIDs: Type: String PrivateSubnetIDs: Type: CommaDelimitedList QNlbPrivateSubnetIDs: Type: CommaDelimitedList DomainName: Type: String QDr: Type: String QFloatRecordName: Type: String QNodeCount: Type: String QMaxNodesDown: Type: String QDiskConfig: Type: String QFlashType: Type: String QFlashTput: Type: String QFlashIops: Type: String QAmiID: Type: String QAmiVer: Type: String QMarketPlaceType: Type: String QModOverness: Type: String QFloatingIP: Type: String QClusterName: Type: String QClusterVersion: Type: String QClusterAdminPwd: Type: String NoEcho: "true" QInstanceType: Type: String VolumesEncryptionKey: Type: String QPermissionsBoundary: Type: String QInstanceRecoveryTopic: Type: String QNlbSticky: Type: String QNlbXzone: Type: String QNlbDeregDelay: Type: String QNlbDeregTerm: Type: String QNlbPreserveIP: Type: String QSgCidr1: Type: String QSgCidr2: Type: String QSgCidr3: Type: String QSgCidr4: Type: String TermProtection: Type: String QClusterLocalZone: Type: String QAuditLog: Type: String RequireIMDSv2: Type: String AllowedValues: - "YES" - "NO" SideCarPrivateSubnetID: Type: String SideCarProv: Type: String SideCarVersion: Type: String SideCarUsername: Type: String SideCarPassword: Type: String NoEcho: "true" SideCarSNSTopic: Type: String TopStackName: Type: String Conditions: UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart'] ProvFlashIops: !Not - !Equals - !Ref QFlashIops - "Use Qumulo Default" ProvFlashTput: !Not - !Equals - !Ref QFlashTput - "Use Qumulo Default" ProvSg2: !Not - !Equals - !Ref QSgCidr2 - "" ProvSg3: !Not - !Equals - !Ref QSgCidr3 - "" ProvSg4: !Not - !Equals - !Ref QSgCidr4 - "" AddQSgCidr: !Or [Condition: ProvSg2, Condition: ProvSg3, Condition: ProvSg4] ProvMultiAZ: !Not - !Equals - !Ref NumberAZs - "1" ProvFloatIP: !Not - !Equals - !Ref QFloatingIP - "0" ProvDNS: !Not - !Equals - !Ref DomainName - "" ProvNLB: !Not - !Equals - !Join [",", !Ref QNlbPrivateSubnetIDs] - "" ProvR53: !And [Condition: ProvDNS, !Not [Condition: ProvMultiAZ], !Not [Condition: ProvNLB], Condition: ProvFloatIP] ProvSideCar: !Equals - !Ref SideCarProv - "YES" PubMgmt: !Not - !Equals - !Ref QPublicMgmt - "NO" LocalAZ: !Not - !Equals - !Ref QClusterLocalZone - "NO" ProvMgmt: !And [Condition: PubMgmt, !Not [Condition: LocalAZ]] CustomAMI: !Equals - !Ref QMarketPlaceType - "Specified-AMI-ID" CustomMP: !Or - !Equals - !Ref QMarketPlaceType - "Custom-1TB-6PB" - !Equals - !Ref QMarketPlaceType - "Specified-AMI-ID" CustomDC: !Not - !Equals - !Ref QDiskConfig - "Select for Custom Offering" CustomNC: !Not - !Equals - !Ref QNodeCount - "Select for Custom Offering OR Expanding Cluster" LookupAMI: !Not [Condition: CustomAMI] Custom: !And [Condition: CustomMP, Condition: CustomDC, Condition: CustomNC] Mappings: ConfigMap: 1TB-Usable-All-Flash: DiskConfig: 600GiB-AF NodeCount: "4" ShortName: 1TB 12TB-Usable-Hybrid-st1: DiskConfig: 5TB-Hybrid-st1 NodeCount: "4" ShortName: 12TB 96TB-Usable-Hybrid-st1: DiskConfig: 20TB-Hybrid-st1 NodeCount: "6" ShortName: 96TB 103TB-Usable-All-Flash: DiskConfig: 30TB-AF NodeCount: "5" ShortName: 103TB 270TB-Usable-Hybrid-st1: DiskConfig: 55TiB-Hybrid-st1 NodeCount: "6" ShortName: 270TB 809TB-Usable-Hybrid-st1: DiskConfig: 160TiB-Hybrid-st1 NodeCount: "6" ShortName: 809TB Custom-1TB-6PB: DiskConfig: CUSTOM-ERROR--NEED-TO-SELECT-DISK-CONFIG NodeCount: CUSTOM-ERROR--NEED-TO-SELECT-NODE-COUNT ShortName: Custom Specified-AMI-ID: DiskConfig: SPECIFIED-AMI-ID-ERROR--NEED-TO-SELECT-DISK-CONFIG NodeCount: SPECIFIED-AMI-ID-ERROR--NEED-TO-SELECT-NODE-COUNT ShortName: Custom Resources: SECRETSSTACK: Type: "AWS::CloudFormation::Stack" Properties: Parameters: ClusterAdminPwd: !Ref QClusterAdminPwd SideCarPassword: !If [ProvSideCar, !Ref SideCarPassword, ""] SideCarUsername: !If [ProvSideCar, !Ref SideCarUsername, ""] TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/store-secrets.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 QLOOKUPSTACK: Type: "AWS::CloudFormation::Stack" Condition: LookupAMI Properties: Parameters: MarketPlaceType: !FindInMap [ConfigMap, !Ref QMarketPlaceType, ShortName] TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/qami-id-lookup-${QAmiVer}.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 QIAMSTACK: Type: "AWS::CloudFormation::Stack" Properties: Parameters: QPermissionsBoundary: !Ref QPermissionsBoundary VolumesEncryptionKey: !Ref VolumesEncryptionKey TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/qiam.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 QSTACK: Type: 'AWS::CloudFormation::Stack' Properties: Parameters: BucketName: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] BucketRegion: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] KeyPair: !Ref KeyPair KeyPrefix: !Ref QSS3KeyPrefix NumberAZs: !Ref NumberAZs PrivateSubnetIDs: !Join [",", !Ref PrivateSubnetIDs] QAmiID: !If [LookupAMI, !GetAtt QLOOKUPSTACK.Outputs.AmiId, !Ref QAmiID] QClusterName: !Ref QClusterName QDiskConfig: !If [Custom, !Ref QDiskConfig, !FindInMap [ConfigMap, !Ref QMarketPlaceType, DiskConfig]] QFlashType: !Ref QFlashType QFloatingIP: !Ref QFloatingIP QInstanceIAMProfile: !GetAtt QIAMSTACK.Outputs.QumuloIAMProfile QInstanceRecoveryTopic: !Ref QInstanceRecoveryTopic QInstanceType: !Ref QInstanceType QNodeCount: !If [CustomNC, !Ref QNodeCount, !FindInMap [ConfigMap, !Ref QMarketPlaceType, NodeCount]] QSgCidr1: !Ref QSgCidr1 RequireIMDSv2: !Ref RequireIMDSv2 VPCId: !Ref VPCId VolumesEncryptionKey: !Ref VolumesEncryptionKey TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/qcluster.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 QADDCIDRSTACK: Type: 'AWS::CloudFormation::Stack' Condition: AddQSgCidr Properties: Parameters: QSgCidr2: !Ref QSgCidr2 QSgCidr3: !Ref QSgCidr3 QSgCidr4: !Ref QSgCidr4 QSgId: !GetAtt QSTACK.Outputs.ClusterSGID TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/qadd-sg-cidrs.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 QSIDECARSTACK: Type: 'AWS::CloudFormation::Stack' Condition: ProvSideCar Properties: Parameters: Hosts: !GetAtt QSTACK.Outputs.ClusterPrivateIPs Password: !Ref SideCarPassword SNSTopic: !Ref SideCarSNSTopic SecurityGroup: !GetAtt QSTACK.Outputs.ClusterSGID Subnet: !If [LocalAZ, !Ref SideCarPrivateSubnetID, !Select [0, !Ref PrivateSubnetIDs]] Username: !Ref SideCarUsername TemplateURL: !Sub "https://qumulo-sidecar-us-east-1.s3.amazonaws.com/${SideCarVersion}/sidecar_cft.json" TimeoutInMinutes: 150 PROVISIONINGSTACK: Type: 'AWS::CloudFormation::Stack' Properties: Parameters: BucketName: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] BucketRegion: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] CMK: !Ref VolumesEncryptionKey ClusterPwd: !GetAtt QSTACK.Outputs.TemporaryPassword ClusterSecretsArn: !GetAtt SECRETSSTACK.Outputs.ClusterSecretsArn FlashIops: !If [ProvFlashIops, !Ref QFlashIops, !GetAtt QSTACK.Outputs.FlashIOPS] FlashTput: !If [ProvFlashTput, !Ref QFlashTput, "250"] FlashType: !Ref QFlashType FloatIPs: !If [ProvFloatIP, !GetAtt QSTACK.Outputs.ClusterSecondaryPrivateIPs, ""] InstanceIDs: !GetAtt QSTACK.Outputs.ClusterInstanceIDs KeyName: !Ref KeyPair KeyPrefix: !Ref QSS3KeyPrefix MaxNodesDown: !If [ProvMultiAZ, !Ref QMaxNodesDown, "1"] ModOverness: !If [ProvMultiAZ, !Ref QModOverness, "NO"] Node1IP: !Select [0, !Split [", ", !GetAtt QSTACK.Outputs.ClusterPrivateIPs]] NodeIPs: !GetAtt QSTACK.Outputs.ClusterPrivateIPs NumberAZs: !If [ProvMultiAZ, !Ref NumberAZs, "1"] PrivateSubnetCidr: !Ref QSgCidr1 PrivateSubnetId: !Select [0, !Ref PrivateSubnetIDs] QClusterName: !Ref QClusterName QClusterVersion: !Ref QClusterVersion QPermissionsBoundary: !Ref QPermissionsBoundary QStackName: !GetAtt QSTACK.Outputs.AWSStackName Region: !Ref AWS::Region RequireIMDSv2: !Ref RequireIMDSv2 SideCarProv: !Ref SideCarProv SideCarSecretsArn: !GetAtt SECRETSSTACK.Outputs.SideCarSecretsArn SoftwareSecretsArn: !GetAtt SECRETSSTACK.Outputs.SoftwareSecretsArn TermProtection: !Ref TermProtection TopStackName: !Ref TopStackName VPCID: !Ref VPCId TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/provisioning-node.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 CLOUDWATCHSTACK: Type: 'AWS::CloudFormation::Stack' Properties: Parameters: AllFlash: !Select [1, !Split ["-", !If [Custom, !Ref QDiskConfig, !FindInMap [ConfigMap, !Ref QMarketPlaceType, DiskConfig]]]] QAuditLog: !Ref QAuditLog QClusterName: !Ref QClusterName QNodeNames: !GetAtt QSTACK.Outputs.ClusterNodeNames QStackName: !GetAtt QSTACK.Outputs.AWSStackName Region: !Ref AWS::Region TopStackName: !Ref TopStackName TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/cloud-watch.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 MGMTNLBSTACK: Type: 'AWS::CloudFormation::Stack' Condition: ProvMgmt Properties: Parameters: EnableReplication: !Ref QPublicRepl NodeIPs: !GetAtt QSTACK.Outputs.ClusterPrivateIPs NumNodes: !If [CustomNC, !Ref QNodeCount, !FindInMap [ConfigMap, !Ref QMarketPlaceType, NodeCount]] PublicSubnetIDs: !Ref PublicSubnetIDs VPCID: !Ref VPCId TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/mgmt-nlb.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 QNLBSTACK: Type: 'AWS::CloudFormation::Stack' Condition: ProvNLB Properties: Parameters: CrossZone: !Ref QNlbXzone DeregDelay: !Ref QNlbDeregDelay DeregTerm: !Ref QNlbDeregTerm NodeIPs: !GetAtt QSTACK.Outputs.ClusterPrivateIPs NumNodes: !If [CustomNC, !Ref QNodeCount, !FindInMap [ConfigMap, !Ref QMarketPlaceType, NodeCount]] PreserveIP: !Ref QNlbPreserveIP PrivateSubnetIDs: !Join [",", !Ref QNlbPrivateSubnetIDs] ProxyProtoV2: "false" Stickiness: !Ref QNlbSticky VPCID: !Ref VPCId TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/qnlb.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 DNSSTACK: Type: 'AWS::CloudFormation::Stack' Condition: ProvR53 Properties: Parameters: FQDName: !Ref DomainName FloatIPs: !GetAtt QSTACK.Outputs.ClusterSecondaryPrivateIPs NumIPs: !Ref QFloatingIP NumNodes: !If [CustomNC, !Ref QNodeCount, !FindInMap [ConfigMap, !Ref QMarketPlaceType, NodeCount]] RecordName: !Ref QFloatRecordName Region: !Ref AWS::Region VPCId: !Ref VPCId TemplateURL: !Sub - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cfn/r53-private-zone.cft.yaml - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] TimeoutInMinutes: 150 Outputs: QumuloPrivateIP: Description: Private IP for Qumulo Cluster Management - Node 1 Value: !GetAtt QSTACK.Outputs.LinkToManagement QumuloNLBPublicURL: Condition: ProvMgmt Description: Public URL for Management NLB connected to Qumulo Cluster Value: !GetAtt MGMTNLBSTACK.Outputs.NLBDNS QumuloPrivateNFS: Condition: ProvR53 Description: Private NFS path for Qumulo Cluster Value: !GetAtt DNSSTACK.Outputs.ClusterNFS QumuloPrivateSMB: Condition: ProvR53 Description: Private SMB UNC path for Qumulo Cluster Value: !GetAtt DNSSTACK.Outputs.ClusterSMB QumuloPrivateURL: Condition: ProvR53 Description: Private URL for Qumulo Cluster Value: !GetAtt DNSSTACK.Outputs.ClusterDNS QumuloNLBPrivateNFS: Condition: ProvNLB Description: Private NFS path for Qumulo Cluster Value: !GetAtt QNLBSTACK.Outputs.NLBNFS QumuloNLBPrivateSMB: Condition: ProvNLB Description: Private SMB UNC path for Qumulo Cluster Value: !GetAtt QNLBSTACK.Outputs.NLBSMB QumuloNLBPrivateURL: Condition: ProvNLB Description: Private URL for NLB Connected to Qumulo Cluster Value: !GetAtt QNLBSTACK.Outputs.NLBDNS QumuloKnowledgeBase: Description: Qumulo Knowledge Base Value: !GetAtt QSTACK.Outputs.QumuloKnowledgeBase