AWSTemplateFormatVersion: '2010-09-09' Description: This main template creates a multi-AZ deployment of a StarWind VSAN on AWS in an existing VPC. It deploys 3 StarWind VSAN EC2 instances. Two nodes and a witness. **WARNING** This template creates EC2 instances and related resources. You will be billed for the AWS resources used if you create a stack from this template. (qs-1sgqsectt) Metadata: cfn-lint: config: ignore_checks: - W9006 - W9901 - E1029 - E9101 QuickStartDocumentation: EntrypointName: "Parameters for deploying into an existing VPC" Order: "2" AWS::CloudFormation::Interface: ParameterGroups: - Label: default: VPC configuration Parameters: - VPCID - PrivateSubnetAID - PrivateSubnetBID - PrivateSubnetCID - RDPSGID - Label: default: Amazon EC2 configuration Parameters: - WorkloadInstanceType - SWVSANAMIOS - SWVSANStorageVolumeSize - KeyPairName - Node1NetBiosName - Node2NetBiosName - WitnessNodeNetBiosName - Label: default: AWS Quick Start configuration Parameters: - QSS3BucketName - QSS3BucketRegion - QSS3KeyPrefix ParameterLabels: KeyPairName: default: Key-pair name VPCID: default: VPC ID PrivateSubnetAID: default: Node 1 subnet PrivateSubnetBID: default: Node 2 subnet PrivateSubnetCID: default: Witness subnet RDPSGID: default: RDP instances security group ID QSS3BucketName: default: Quick Start S3 bucket name QSS3BucketRegion: default: Quick Start S3 bucket Region QSS3KeyPrefix: default: Quick Start S3 key prefix WorkloadInstanceType: default: Workload servers instance type SWVSANAMIOS: default: Workload servers OS version SWVSANStorageVolumeSize: default: Secondary EBS volume size Node1NetBiosName: default: Node 1 NetBIOS name Node2NetBiosName: default: Node 2 NetBIOS name WitnessNodeNetBiosName: default: Witness node NetBIOS name Parameters: KeyPairName: Description: Name of an existing EC2 key pair. The instance will launch with this key pair. Type: AWS::EC2::KeyPair::KeyName VPCID: Description: ID of your existing VPC for deployment. Type: AWS::EC2::VPC::Id PrivateSubnetAID: Description: ID of private subnet in Availability Zone 1. for the first node (e.g., subnet-a0246dcd). Type: AWS::EC2::Subnet::Id PrivateSubnetBID: Description: ID of private subnet in Availability Zone 2. for the second node (e.g., subnet-a0246dcd). Type: AWS::EC2::Subnet::Id PrivateSubnetCID: Description: ID of private subnet in Availability Zone 3. for the witness (e.g., subnet-a0246dcd). Type: AWS::EC2::Subnet::Id RDPSGID: Description: ID of the security group for your Remote Desktop instances. Type: AWS::EC2::SecurityGroup::Id QSS3BucketName: AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$ ConstraintDescription: The Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Default: aws-quickstart Description: Name of the S3 bucket for your copy of the Quick Start assets. Keep the default name unless you are customizing the template. Changing the name updates code references to point to a new Quick Start location. This name can include numbers, lowercase letters, uppercase letters, and hyphens, but do not start or end with a hyphen (-). See https://aws-quickstart.github.io/option1.html. Type: String QSS3BucketRegion: Default: 'us-east-1' Description: 'AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. Keep the default Region unless you are customizing the template. Changing this Region updates code references to point to a new Quick Start location. When using your own bucket, specify the Region. See https://aws-quickstart.github.io/option1.html.' Type: String QSS3KeyPrefix: AllowedPattern: ^[0-9a-zA-Z-/]*$ ConstraintDescription: The Quick Start S3 key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slashes (/). The prefix should end with a forward slash (/). Default: quickstart-starwind-vsan/ Description: S3 key prefix that is used to simulate a folder for your copy of the Quick Start assets. Keep the default prefix unless you are customizing the template. Changing this prefix updates code references to point to a new Quick Start location. This prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slashes (/). End with a forward slash. See https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html and https://aws-quickstart.github.io/option1.html. Type: String WorkloadInstanceType: AllowedValues: - c6i.12xlarge - c6i.16xlarge - c6i.24xlarge - c6i.32xlarge - c6i.2xlarge - c6i.4xlarge - c6i.8xlarge - c6i.metal - c6id.16xlarge - c6id.32xlarge - c6id.2xlarge - c6id.4xlarge - c6id.8xlarge - c5.12xlarge - c5.18xlarge - c5.24xlarge - c5.2xlarge - c5.4xlarge - c5.9xlarge - c5.metal - c5d.18xlarge - c5d.2xlarge - c5d.4xlarge - c5d.9xlarge - c5n.18xlarge - c5n.2xlarge - c5n.4xlarge - c5n.9xlarge - c5n.metal - d2.2xlarge - d2.4xlarge - d2.8xlarge - f1.16xlarge - g2.2xlarge - g2.8xlarge - g3.16xlarge - g3.4xlarge - g3.8xlarge - g4dn.12xlarge - g4dn.16xlarge - g4dn.2xlarge - g4dn.4xlarge - g4dn.8xlarge - h1.16xlarge - h1.2xlarge - h1.4xlarge - h1.8xlarge - hs1.8xlarge - i2.2xlarge - i2.4xlarge - i2.8xlarge - i3.16xlarge - i3.2xlarge - i3.4xlarge - i3.8xlarge - i3.metal - i3en.12xlarge - i3en.24xlarge - i3en.2xlarge - i3en.3xlarge - i3en.6xlarge - i3en.large - i3en.metal - i3en.xlarge - m5.12xlarge - m5.16xlarge - m5.24xlarge - m5.2xlarge - m5.xlarge - m5.4xlarge - m5.8xlarge - m5.metal - m5a.12xlarge - m5a.16xlarge - m5a.24xlarge - m5a.2xlarge - m5a.4xlarge - m5a.8xlarge - m5ad.12xlarge - m5ad.24xlarge - m5ad.2xlarge - m5ad.4xlarge - m5d.12xlarge - m5d.16xlarge - m5d.24xlarge - m5d.2xlarge - m5d.4xlarge - m5d.8xlarge - m5d.metal - m5dn.12xlarge - m5dn.16xlarge - m5dn.24xlarge - m5dn.2xlarge - m5dn.4xlarge - m5dn.8xlarge - m5n.12xlarge - m5n.16xlarge - m5n.24xlarge - m5n.2xlarge - m5n.4xlarge - m5n.8xlarge - p2.16xlarge - p2.8xlarge - p3.16xlarge - p3.2xlarge - p3.8xlarge - p3dn.24xlarge - r5.12xlarge - r5.16xlarge - r5.24xlarge - r5.2xlarge - r5.4xlarge - r5.8xlarge - r5.metal - r5a.12xlarge - r5a.16xlarge - r5a.24xlarge - r5a.2xlarge - r5a.4xlarge - r5a.8xlarge - r5ad.12xlarge - r5ad.24xlarge - r5ad.2xlarge - r5ad.4xlarge - r5d.12xlarge - r5d.16xlarge - r5d.24xlarge - r5d.2xlarge - r5d.4xlarge - r5d.8xlarge - r5d.metal - r5dn.12xlarge - r5dn.16xlarge - r5dn.24xlarge - r5dn.2xlarge - r5dn.4xlarge - r5dn.8xlarge - r5n.12xlarge - r5n.16xlarge - r5n.24xlarge - r5n.2xlarge - r5n.4xlarge - r5n.8xlarge - t3.2xlarge - t3a.2xlarge - x1.16xlarge - x1.32xlarge - x1e.16xlarge - x1e.2xlarge - x1e.32xlarge - x1e.4xlarge - x1e.8xlarge - z1d.12xlarge - z1d.2xlarge - z1d.3xlarge - z1d.6xlarge - z1d.metal ConstraintDescription: Must contain a valid instance type. Default: m5.xlarge Description: Type of EC2 instance for the workload instances. Type: String SWVSANAMIOS: Type: String Description: Operating system version of the StarWind instances to be created. Default: WindowsServer2019 AllowedValues: - WindowsServer2019 - WindowsServer2016 SWVSANStorageVolumeSize: Type: Number Default: 100 ConstraintDescription: Must be between 1 GB and 16,000 GB (16 TB). MinValue: 1 MaxValue: 16000 Description: Size (in GB) for StarWind VSAN virtualized storage. Node1NetBiosName: AllowedPattern: '[a-zA-Z0-9\-]+' Default: "SWVSANNODE1" Description: "NetBIOS name of storage node 1 (up to 15 characters)." MaxLength: '15' MinLength: '1' Type: "String" Node2NetBiosName: AllowedPattern: '[a-zA-Z0-9\-]+' Default: "SWVSANNODE2" Description: "NetBIOS name of storage node 2 (up to 15 characters)." MaxLength: '15' MinLength: '1' Type: "String" WitnessNodeNetBiosName: AllowedPattern: '[a-zA-Z0-9\-]+' Default: "SWVSANWIT" Description: "NetBIOS name of witness node (up to 15 characters)." MaxLength: '15' MinLength: '1' Type: "String" Conditions: UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart'] Resources: SSMWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle SSMWaitCondition: Type: AWS::CloudFormation::WaitCondition CreationPolicy: ResourceSignal: Timeout: PT20M Count: 1 DependsOn: "StorageNode1Stack" Properties: Handle: !Ref SSMWaitHandle Timeout: "1200" Count: 1 SWVSANQuickstartSSMAutomation: Type: AWS::SSM::Document Properties: DocumentType: Automation Content: description: '' schemaVersion: '0.3' assumeRole: '{{AutomationAssumeRole}}' parameters: StackName: type: String description: The CloudFormation stack name this automation belongs to Node1Name: type: String description: NetBIOS name for storage node 1 Node2Name: type: String description: NetBIOS name for storage node 2 WitnessNodeName: type: String description: NetBIOS name for witness node AutomationAssumeRole: type: String description: |- (Optional) The ARN of the role that allows automation to perform the actions on your behalf mainSteps: - name: "swvsanNode1InstanceId" action: aws:executeAwsApi onFailure: "step:signalfailure" nextStep: swvsanNode2InstanceId inputs: Service: ec2 Api: DescribeInstances Filters: - Name: "tag:Name" Values: ["StarWind Node 1"] - Name: "tag:qs-sw-vsan:parent-stack-name" Values: ["{{StackName}}"] outputs: - Name: InstanceId Selector: "$.Reservations[0].Instances[0].InstanceId" Type: String - name: "swvsanNode2InstanceId" action: aws:executeAwsApi onFailure: "step:signalfailure" nextStep: swvsanWitnessInstanceId inputs: Service: ec2 Api: DescribeInstances Filters: - Name: "tag:Name" Values: ["StarWind Node 2"] - Name: "tag:qs-sw-vsan:parent-stack-name" Values: ["{{StackName}}"] outputs: - Name: InstanceId Selector: "$.Reservations[0].Instances[0].InstanceId" Type: String - name: "swvsanWitnessInstanceId" action: aws:executeAwsApi onFailure: "step:signalfailure" nextStep: WaitForNode1 inputs: Service: ec2 Api: DescribeInstances Filters: - Name: "tag:Name" Values: ["StarWind Witness"] - Name: "tag:qs-sw-vsan:parent-stack-name" Values: ["{{StackName}}"] outputs: - Name: InstanceId Selector: "$.Reservations[0].Instances[0].InstanceId" Type: String - name: WaitForNode1 action: 'aws:waitForAwsResourceProperty' nextStep: WaitForNode2 inputs: Service: ec2 Api: DescribeInstanceStatus PropertySelector: '$.InstanceStatuses[0].InstanceState.Name' DesiredValues: - running InstanceIds: - '{{swvsanNode1InstanceId.InstanceId}}' timeoutSeconds: 60 - name: WaitForNode2 action: 'aws:waitForAwsResourceProperty' nextStep: WaitForWitness inputs: Service: ec2 Api: DescribeInstanceStatus PropertySelector: '$.InstanceStatuses[0].InstanceState.Name' DesiredValues: - running InstanceIds: - '{{swvsanNode2InstanceId.InstanceId}}' timeoutSeconds: 60 - name: WaitForWitness action: 'aws:waitForAwsResourceProperty' nextStep: renameComputer inputs: Service: ec2 Api: DescribeInstanceStatus PropertySelector: '$.InstanceStatuses[0].InstanceState.Name' DesiredValues: - running InstanceIds: - '{{swvsanWitnessInstanceId.InstanceId}}' timeoutSeconds: 60 - name: renameComputer action: aws:runCommand nextStep: EnableIscsiMpio onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{swvsanNode1InstanceId.InstanceId}}" - "{{swvsanNode2InstanceId.InstanceId}}" - "{{swvsanWitnessInstanceId.InstanceId}}" # CloudWatchOutputConfig: # CloudWatchOutputEnabled: "true" # CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}' Parameters: commands: - | $currentNodeInstanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing | Select -expand "Content" $currentNodeName = Get-EC2Tag -Filter @{Name="resource-type";Values="instance"},@{Name="resource-id";Values="$currentNodeInstanceId"},@{Name="key";Values="Name"} -Select 'Tags.Value' $newName = switch ( $currentNodeName ) { 'StarWind Node 1' { '{{Node1Name}}' } 'StarWind Node 2' { '{{Node2Name}}' } 'StarWind Witness' { '{{WitnessNodeName}}' } } # get current computer name $currentName = (gwmi WIN32_ComputerSystem).Name # if name is not updated if ($currentName -ne $newName) { # change computer name Rename-Computer -NewName $newName # Tell SSM to reboot. exit 3010 } echo "New name is: $currentName" - name: EnableIscsiMpio action: aws:runCommand nextStep: StaticIPsConfig onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{swvsanNode1InstanceId.InstanceId}}" - "{{swvsanNode2InstanceId.InstanceId}}" # - "{{swvsanWitnessInstanceId.InstanceId}}" # CloudWatchOutputConfig: # CloudWatchOutputEnabled: "true" # CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}' Parameters: commands: - | # Get iscsi service info $iscsi = Get-Service MSiSCSI # if startup type is not automatic if ($iscsi.StartType -ne "Automatic") { # Set start type to automatic $iscsi | Set-Service -StartType Automatic # start the service (incase it is stopped) Start-Service MSiSCSI } # Get MPIO feature info $mpio = Get-WindowsFeature -Name Multipath-IO # if its not installed if (-not $mpio.Installed) { # install the MPIO feature Install-WindowsFeature -Name Multipath-IO # Tell SSM to reboot. exit 3010 } # enable MPIO on iSCSI Enable-MSDSMAutomaticClaim -BusType "iSCSI" - name: StaticIPsConfig action: aws:runCommand nextStep: StarWindEBSVolumeConfig onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{swvsanNode1InstanceId.InstanceId}}" - "{{swvsanNode2InstanceId.InstanceId}}" # - "{{swvsanWitnessInstanceId.InstanceId}}" # CloudWatchOutputConfig: # CloudWatchOutputEnabled: "true" # CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}' Parameters: commands: - | # Disable windows server firewall to allow inter-node communication Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False $currentNodeInstanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing | Select -expand "Content" $ENInames = "Management/Witness ENI","iSCSI ENI","Syncronization ENI" # storing commands in an array as changing network config on the fly causes IAM errors $setupCommands = new-object collections.generic.list[object] foreach ($eni in $ENInames) { $eniIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$currentNodeInstanceId"},@{Name="description";Values="$eni"} -Select 'NetworkInterfaces.PrivateIpAddress' $ethernetDeviceName = (Get-NetIPAddress "$eniIp").InterfaceAlias $networkInterfaceIndex = (Get-NetIPAddress "$eniIp").InterfaceIndex $dnsIp = (Get-NetIPConfiguration -InterfaceIndex (Get-NetIPAddress "$eniIp").InterfaceIndex).DNSServer.ServerAddresses $defaultGateway = (Get-NetIPConfiguration -InterfaceIndex (Get-NetIPAddress "$eniIp").InterfaceIndex).IPv4DefaultGateway.NextHop $subnetMask = (Get-WmiObject Win32_NetworkAdapterConfiguration | Where IPAddress -eq "$eniIp").IPSubnet[0] $eni $eniIp $ethernetDeviceName $networkInterfaceIndex $dnsIp $defaultGateway $subnetMask if ($eni -eq "Management/Witness ENI") { # For Management I need to add Gateway $setupCommands.Add("netsh interface ipv4 set address name='$ethernetDeviceName' static $eniIp $subnetMask $defaultGateway") } else { # For iSCSI and Sync (no default gateway) $setupCommands.Add("netsh interface ipv4 set address name='$ethernetDeviceName' static $eniIp $subnetMask") } # # Setup DNS Server $setupCommands.Add("Set-DnsClientServerAddress -InterfaceIndex $networkInterfaceIndex -ServerAddresses ('$dnsIp')") if ($eni -eq "Syncronization ENI") { $currentNodeName = Get-EC2Tag -Filter @{Name="resource-type";Values="instance"},@{Name="resource-id";Values="$currentNodeInstanceId"},@{Name="key";Values="Name"} -Select 'Tags.Value' $otherNodeName = "StarWind Node 2" if ($currentNodeName -eq $otherNodeName) { $otherNodeName = "StarWind Node 1" } $otherNodeInstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="$otherNodeName"} -Select 'Reservations.Instances.InstanceId' # Get other node Syncronization ENI IP $otherNodeSyncENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$otherNodeInstanceId"},@{Name="description";Values="Syncronization ENI"} -Select 'NetworkInterfaces.PrivateIpAddress' # for primary and secondary storage nodes to talk to each other over Sync ENI $setupCommands.Add("ROUTE -p ADD $otherNodeSyncENIIp MASK 255.255.255.255 $defaultGateway METRIC 15 IF $networkInterfaceIndex") } elseif ($eni -eq "iSCSI ENI") { # Route for ISCSI ENI to talk to clients with low Metric (so it is only used when ENI/IP is specified as source) $setupCommands.Add("ROUTE -p ADD 0.0.0.0 MASK 0.0.0.0 $defaultGateway METRIC 100 IF $networkInterfaceIndex") } } foreach ($command in $setupCommands) { Invoke-Expression $command } - name: StarWindEBSVolumeConfig action: aws:runCommand nextStep: CreateStarWindQuickStartDirectoryOnWitness onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{swvsanNode1InstanceId.InstanceId}}" - "{{swvsanNode2InstanceId.InstanceId}}" # CloudWatchOutputConfig: # CloudWatchOutputEnabled: "true" # CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}' Parameters: commands: - | # Initialze disk (Secondary EBS volume) Initialize-Disk -Number 1 -PartitionStyle MBR # Create partition using all space as D drive New-Partition –DiskNumber 1 -DriveLetter D –UseMaximumSize Format-Volume -DriveLetter D -FileSystem NTFS -Confirm:$false # Create folder in D drive to store the StarWind HA device image New-Item -ItemType Directory -Path D:\StarWind-QuickStart - name: CreateStarWindQuickStartDirectoryOnWitness action: aws:runCommand nextStep: StarWindVSANConfig onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{swvsanWitnessInstanceId.InstanceId}}" # CloudWatchOutputConfig: # CloudWatchOutputEnabled: "true" # CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}' Parameters: commands: - | New-Item -ItemType Directory -Path C:\StarWind-QuickStart - name: StarWindVSANConfig action: aws:runCommand nextStep: ConnectIscsiStarWindDevices onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{swvsanNode1InstanceId.InstanceId}}" # CloudWatchOutputConfig: # CloudWatchOutputEnabled: "true" # CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}' Parameters: commands: - | $currentNodeInstanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing | Select -expand "Content" $otherNodeName = "StarWind Node 2" $otherNodeInstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="$otherNodeName"} -Select 'Reservations.Instances.InstanceId' $WitnessNodeName = "StarWind Witness" $witnessNodeInstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="$WitnessNodeName"} -Select 'Reservations.Instances.InstanceId' # Get node 1 Syncronization ENI IP $currentNodeSyncENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$currentNodeInstanceId"},@{Name="description";Values="Syncronization ENI"} -Select 'NetworkInterfaces.PrivateIpAddress' # Get node 1 Management ENI IP $currentNodeManagementENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$currentNodeInstanceId"},@{Name="description";Values="Management/Witness ENI"} -Select 'NetworkInterfaces.PrivateIpAddress' # Get node 2 Syncronization ENI IP $otherNodeSyncENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$otherNodeInstanceId"},@{Name="description";Values="Syncronization ENI"} -Select 'NetworkInterfaces.PrivateIpAddress' # Get node 2 Management ENI IP $otherNodeManagementENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$otherNodeInstanceId"},@{Name="description";Values="Management/Witness ENI"} -Select 'NetworkInterfaces.PrivateIpAddress' # Get witness node Management ENI IP $witnessNodeManagementENIIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$witnessNodeInstanceId"},@{Name="description";Values="Management/Witness ENI"} -Select 'NetworkInterfaces.PrivateIpAddress' $first_node_csv = "iqn.2008-08.com.starwindsoftware:" + "{{Node1Name}}".ToLower() + "-csv" $second_node_csv = "iqn.2008-08.com.starwindsoftware:" + "{{Node2Name}}".ToLower() + "-csv" $witness_node_csv = "iqn.2008-08.com.starwindsoftware:" + "{{WitnessNodeName}}".ToLower() + "-csv" &"C:\Program Files\StarWind Software\StarWind\StarWindX\Samples\powershell\Create_HA_Node_Majority.ps1" -addr $currentNodeManagementENIIp -addr2 $otherNodeManagementENIIp -addrW $witnessNodeManagementENIIp -syncInterface "#p2=${otherNodeSyncENIIp}:3260;#p3=${witnessNodeManagementENIIp}:3260" -syncInterface2 "#p1=${currentNodeSyncENIIp}:3260;#p3=${witnessNodeManagementENIIp}:3260" -syncInterfaceW "#p1=${currentNodeManagementENIIp}:3260;#p2=${otherNodeManagementENIIp}:3260" -imagePath "D:\StarWind-QuickStart" -imagePath2 "D:\StarWind-QuickStart" -imagePathW "C:\StarWind-QuickStart" -targetAlias "csv" -targetAlias2 "csv" -targetAliasW "csv" -targetName $first_node_csv -targetName2 $second_node_csv -targetNameW $witness_node_csv -cacheMode $null $freeDiskSpace = Get-PSDrive D | Select-Object @{Expression={$_.Free/1MB}} | Select -expand '$_.Free/1MB' # Extend the HA device to the full size of the volume &"C:\Program Files\StarWind Software\StarWind\StarWindX\Samples\powershell\ExtendDevice.ps1" -addr $currentNodeSyncENIIp -deviceName "HAImage1" -extendSize $freeDiskSpace - name: ConnectIscsiStarWindDevices action: aws:runCommand nextStep: CreateVolumeOnIscsiStarWindDevices onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{swvsanNode1InstanceId.InstanceId}}" - "{{swvsanNode2InstanceId.InstanceId}}" # CloudWatchOutputConfig: # CloudWatchOutputEnabled: "true" # CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}' Parameters: commands: - | $first_node_csv = "iqn.2008-08.com.starwindsoftware:" + "{{Node1Name}}".ToLower() + "-csv" $second_node_csv = "iqn.2008-08.com.starwindsoftware:" + "{{Node2Name}}".ToLower() + "-csv" $node1InstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="StarWind Node 1"} -Select 'Reservations.Instances.InstanceId' $node2InstanceId = Get-EC2Instance -Filter @{Name="tag:qs-sw-vsan:parent-stack-name";Values="{{StackName}}"},@{Name="tag:Name";Values="StarWind Node 2"} -Select 'Reservations.Instances.InstanceId' $node1IscsiIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$node1InstanceId"},@{Name="description";Values="iSCSI ENI"} -Select 'NetworkInterfaces.PrivateIpAddress' $node2IscsiIp = Get-EC2NetworkInterface -Filter @{Name="attachment.instance-id";Values="$node2InstanceId"},@{Name="description";Values="iSCSI ENI"} -Select 'NetworkInterfaces.PrivateIpAddress' $currentNodeInstanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing | Select -expand "Content" if ($currentNodeInstanceId -eq $node1InstanceId) { $sourceIscsiIP = $node1IscsiIp $destinationIscsiIp = $node2IscsiIp $currentNodeIscsiAddress = $first_node_csv $otherNodeIscsiAddress = $second_node_csv } elseif ($currentNodeInstanceId -eq $node2InstanceId) { $sourceIscsiIP = $node2IscsiIp $destinationIscsiIp = $node1IscsiIp $currentNodeIscsiAddress = $second_node_csv $otherNodeIscsiAddress = $first_node_csv } New-IscsiTargetPortal -TargetPortalAddress 127.0.0.1 -TargetPortalPortNumber 3260 New-IscsiTargetPortal -TargetPortalAddress $destinationIscsiIp -TargetPortalPortNumber 3260 -InitiatorPortalAddress $sourceIscsiIP Connect-IscsiTarget -NodeAddress $currentNodeIscsiAddress -TargetPortalAddress 127.0.0.1 -TargetPortalPortNumber 3260 -IsMultipathEnabled $true -IsPersistent $true Connect-IscsiTarget -NodeAddress $otherNodeIscsiAddress -TargetPortalAddress $destinationIscsiIp -InitiatorPortalAddress $sourceIscsiIP -TargetPortalPortNumber 3260 -IsMultipathEnabled $true -IsPersistent $true Set-MSDSMGlobalDefaultLoadBalancePolicy -Policy LQD # print currently configured iscsi targets Get-IscsiTarget - name: CreateVolumeOnIscsiStarWindDevices action: aws:runCommand nextStep: renameStorageNode1EC2 onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{swvsanNode1InstanceId.InstanceId}}" # CloudWatchOutputConfig: # CloudWatchOutputEnabled: "true" # CloudWatchLogGroupName: !Sub '/aws/Quick_Start/${AWS::StackName}' Parameters: commands: - | $device2ID = Get-Content D:\StarWind-QuickStart\HAimage2.swdsk | Select-Object -Index 7 $device2ID = $device2ID -replace "<serial_id>", "" -replace "</serial_id>", "" $device2ID = $device2ID.Trim() $disk2 = Get-Disk | Where-Object SerialNumber -eq $device2ID | Initialize-Disk -PartitionStyle GPT $diskselection2 = Get-Disk | Where-Object SerialNumber -eq $device2ID | foreach Number New-Volume -FriendlyName CSV -DiskNumber $diskselection2 -FileSystem NTFS -AllocationUnitSize 4096 - name: renameStorageNode1EC2 action: 'aws:executeAwsApi' nextStep: renameStorageNode2EC2 inputs: Service: ec2 Api: CreateTags Resources: - '{{swvsanNode1InstanceId.InstanceId}}' Tags: - Key: Name Value: '{{Node1Name}}' - name: renameStorageNode2EC2 action: 'aws:executeAwsApi' nextStep: renameWitnessNodeEC2 inputs: Service: ec2 Api: CreateTags Resources: - '{{swvsanNode2InstanceId.InstanceId}}' Tags: - Key: Name Value: '{{Node2Name}}' - name: renameWitnessNodeEC2 action: 'aws:executeAwsApi' nextStep: signalsuccess inputs: Service: ec2 Api: CreateTags Resources: - '{{swvsanWitnessInstanceId.InstanceId}}' Tags: - Key: Name Value: '{{WitnessNodeName}}' # If all steps complete successfully signals CFN of Success - name: "signalsuccess" action: "aws:executeAwsApi" isEnd: True inputs: Service: cloudformation Api: SignalResource LogicalResourceId: "SSMWaitCondition" StackName: "{{StackName}}" Status: SUCCESS UniqueId: "{{swvsanNode1InstanceId.InstanceId}}" # If any steps fails signals CFN of Failure - name: "signalfailure" action: "aws:executeAwsApi" inputs: Service: cloudformation Api: SignalResource LogicalResourceId: "SSMWaitCondition" StackName: "{{StackName}}" Status: FAILURE UniqueId: "{{swvsanNode1InstanceId.InstanceId}}" SWVSANSSMIAMRole: Type: AWS::IAM::Role Metadata: cfn-lint: config: ignore_checks: - EIAMPolicyWildcardResource ignore_reason: - "Scope is limited appropriately" Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: - !Sub 'arn:${AWS::Partition}:s3:::aws-ssm-${AWS::Region}/*' - !Sub 'arn:${AWS::Partition}:s3:::aws-windows-downloads-${AWS::Region}/*' - !Sub 'arn:${AWS::Partition}:s3:::amazon-ssm-${AWS::Region}/*' - !Sub 'arn:${AWS::Partition}:s3:::amazon-ssm-packages-${AWS::Region}/*' - !Sub 'arn:${AWS::Partition}:s3:::${AWS::Region}-birdwatcher-prod/*' - !Sub 'arn:${AWS::Partition}:s3:::patch-baseline-snapshot-${AWS::Region}/*' Effect: Allow PolicyName: ssm-custom-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - cloudformation:SignalResource Resource: !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' - Effect: Allow Action: - ec2:DescribeInstances - ec2:DescribeInstanceStatus - ec2:CreateTags - ssm:DescribeInstanceInformation - ssm:ListCommands - ssm:ListCommandInvocations - ssm:SendCommand - ssm:CancelCommand Resource: '*' PolicyName: QS-SWVSAN-SSM-AutomationExecution Path: / AssumeRolePolicyDocument: Statement: - Action: - sts:AssumeRole Principal: Service: - ec2.amazonaws.com - ssm.amazonaws.com Effect: Allow Version: '2012-10-17' SWVSANEC2IAMRole: Type: AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: - !Sub 'arn:${AWS::Partition}:s3:::aws-ssm-${AWS::Region}/*' - !Sub 'arn:${AWS::Partition}:s3:::aws-windows-downloads-${AWS::Region}/*' - !Sub 'arn:${AWS::Partition}:s3:::amazon-ssm-${AWS::Region}/*' - !Sub 'arn:${AWS::Partition}:s3:::amazon-ssm-packages-${AWS::Region}/*' - !Sub 'arn:${AWS::Partition}:s3:::${AWS::Region}-birdwatcher-prod/*' - !Sub 'arn:${AWS::Partition}:s3:::patch-baseline-snapshot-${AWS::Region}/*' Effect: Allow PolicyName: ssm-custom-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ssm:StartAutomationExecution Resource: !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${SWVSANQuickstartSSMAutomation}:$DEFAULT' PolicyName: QS-SWVSAN-SSM-Trigger - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - iam:PassRole Resource: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${SWVSANSSMIAMRole}' PolicyName: QS-SWVSAN-SSM-PassRole Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ReadOnlyAccess' AssumeRolePolicyDocument: Statement: - Action: - sts:AssumeRole Principal: Service: - ec2.amazonaws.com - ssm.amazonaws.com Effect: Allow Version: '2012-10-17' SWVSANEC2IAMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref 'SWVSANEC2IAMRole' Path: / SecurityGroupsStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: !Sub - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/swvsan-securitygroups.template.yaml' - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] Parameters: VPCID: !Ref VPCID ParentStackName: !Sub ${AWS::StackName} RDPSGID: !Ref RDPSGID StorageNode1Stack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: !Sub - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/swvsan-instance.template.yaml' - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] Parameters: WorkloadInstanceType: !Ref WorkloadInstanceType SWVSANAMIOS: !Ref SWVSANAMIOS KeyPairName: !Ref KeyPairName PrivateSubnetID: !Ref PrivateSubnetAID ISCSIENISGId: !GetAtt SecurityGroupsStack.Outputs.ISCSIENISGId ManagementENISGId: !GetAtt SecurityGroupsStack.Outputs.ManagementENISGId SyncronizationENISGId: !GetAtt SecurityGroupsStack.Outputs.SyncronizationENISGId SWVSANStorageVolumeSize: !Ref SWVSANStorageVolumeSize SWVSANEC2InstanceName: 'StarWind Node 1' SWVSANEC2IAMInstanceProfile: !Ref SWVSANEC2IAMInstanceProfile SWVSANEC2IAMInstanceUserData: !Base64 Fn::Join: - '' - - "<powershell>\n" - 'Start-SSMAutomationExecution -DocumentName ' - !Sub '"${SWVSANQuickstartSSMAutomation}"' - ' -Parameter @{"StackName"=' - !Sub '"${AWS::StackName}"' - ';"Node1Name"=' - !Sub '"${Node1NetBiosName}"' - ';"Node2Name"=' - !Sub '"${Node2NetBiosName}"' - ';"WitnessNodeName"=' - !Sub '"${WitnessNodeNetBiosName}"' - ';"AutomationAssumeRole"=' - !Sub '"arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${SWVSANSSMIAMRole}"' - '}' - "\n" - "</powershell>\n" ParentStackName: !Sub ${AWS::StackName} StorageNode2Stack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: !Sub - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/swvsan-instance.template.yaml' - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] Parameters: WorkloadInstanceType: !Ref WorkloadInstanceType SWVSANAMIOS: !Ref SWVSANAMIOS KeyPairName: !Ref KeyPairName PrivateSubnetID: !Ref PrivateSubnetBID ISCSIENISGId: !GetAtt SecurityGroupsStack.Outputs.ISCSIENISGId ManagementENISGId: !GetAtt SecurityGroupsStack.Outputs.ManagementENISGId SyncronizationENISGId: !GetAtt SecurityGroupsStack.Outputs.SyncronizationENISGId SWVSANStorageVolumeSize: !Ref SWVSANStorageVolumeSize SWVSANEC2InstanceName: 'StarWind Node 2' SWVSANEC2IAMInstanceProfile: !Ref SWVSANEC2IAMInstanceProfile ParentStackName: !Sub ${AWS::StackName} WitnessNodeStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: !Sub - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/swvsan-instance.template.yaml' - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] Parameters: NodeOrWitness: Witness WorkloadInstanceType: !Ref WorkloadInstanceType SWVSANAMIOS: !Ref SWVSANAMIOS KeyPairName: !Ref KeyPairName PrivateSubnetID: !Ref PrivateSubnetCID ManagementENISGId: !GetAtt SecurityGroupsStack.Outputs.ManagementENISGId SWVSANStorageVolumeSize: !Ref SWVSANStorageVolumeSize SWVSANEC2InstanceName: 'StarWind Witness' SWVSANEC2IAMInstanceProfile: !Ref SWVSANEC2IAMInstanceProfile ParentStackName: !Sub ${AWS::StackName} Outputs: StorageNode1ManagementIP: Description: Management IP address of storage node 1 Value: !GetAtt StorageNode1Stack.Outputs.SWVSANEC2InstanceManagementIP StorageNode1IscsiIP: Description: iSCSI IP address of storage node 1 Value: !GetAtt StorageNode1Stack.Outputs.SWVSANEC2InstanceIscsiIP StorageNode1SyncronizationIP: Description: Synchronization IP address of storage node 1 Value: !GetAtt StorageNode1Stack.Outputs.SWVSANEC2InstanceSyncronizationIP StorageNode2ManagementIP: Description: Management IP address of storage node 2 Value: !GetAtt StorageNode2Stack.Outputs.SWVSANEC2InstanceManagementIP StorageNode2IscsiIP: Description: iSCSI IP address of storage node 2 Value: !GetAtt StorageNode2Stack.Outputs.SWVSANEC2InstanceIscsiIP StorageNode2SyncronizationIP: Description: Synchronization IP address of storage node 2 Value: !GetAtt StorageNode2Stack.Outputs.SWVSANEC2InstanceSyncronizationIP WitnessNodeManagementIP: Description: Management IP address of witness node Value: !GetAtt WitnessNodeStack.Outputs.SWVSANEC2InstanceManagementIP