---
AWSTemplateFormatVersion: 2010-09-09

Description: Amazon Elastic File System - Creates a file system with data

Metadata:
  Authors:
    Description: Darryl Osborne (darrylo@amazon.com)
  License:
    Description: 'Copyright 2018 Amazon.com, Inc. and its affiliates. All Rights Reserved.
      SPDX-License-Identifier: MIT-0'
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: Amazon EFS Parameters
      Parameters:
        - Growth
        - InstanceType0
        - KeyName
        - Success
        - DeletionPolicy
        - PerformanceMode
        - EncryptionState
        - Cmk
        - SecurityGroup
        - NumberOfSubnets
        - Subnet
    ParameterLabels:
      EncryptionState:
        default: Encryption state
      Cmk:
        default: KMS Key
      DeletionPolicy:
        default: Retain or Delete
      Growth:
        default: Add data (GiB)
      InstanceType0:
        default: Instance Type
      KeyName:
        default: Existing Key Pair
      NumberOfSubnets:
        default: Number of subnets
      PerformanceMode:
        default: Performance Mode
      SecurityGroup:
        default: Security Group
      Subnet:
        default: Subnets
      Success:
        default: "Success on:"

Parameters:
  EncryptionState:
    AllowedValues:
      - Encrypted
      - Unencrypted
    Default: Encrypted
    Description: Create an encrypted or unencrypted file system.
    Type: String
  Cmk:
    Description: An existing AWS KMS Customer Master Key (CMK) to encrypt file system
    Type: String
  DeletionPolicy:
    AllowedValues:
      - Delete
      - Retain
    Default: Delete
    Description: Retain or delete the Amazon EFS resources after CloudFormation stack deletion.
    Type: String
  Growth:
    ConstraintDescription: Must be an integer.
    Default: 0
    Description: Amount of dummy data (GiB) to add to the file system (max 6144 GiB). Amazon EFS storage charges apply. 
    MaxValue: 6144
    MinValue: 0
    Type: Number
  InstanceType0:
    AllowedValues:
      - t2.nano
      - t2.micro
      - t2.small
      - t2.medium
      - t2.large
      - t2.xlarge
      - t2.2xlarge
      - m3.medium
      - m3.large
      - m3.xlarge
      - m3.2xlarge
      - m4.large
      - m4.xlarge
      - m4.2xlarge
      - m4.4xlarge
      - m4.10xlarge
      - m4.16xlarge
      - c3.large
      - c3.xlarge
      - c3.2xlarge
      - c3.4xlarge
      - c3.8xlarge
      - c4.large
      - c4.xlarge
      - c4.2xlarge
      - c4.4xlarge
      - c4.8xlarge
      - c5.large
      - c5.xlarge
      - c5.2xlarge
      - c5.4xlarge
      - c5.8xlarge
      - r3.large
      - r3.xlarge
      - r3.2xlarge
      - r3.4xlarge
      - r3.8xlarge
      - r4.large
      - r4.xlarge
      - r4.2xlarge
      - r4.4xlarge
      - r4.8xlarge
      - r4.16xlarge
      - i3.large
      - i3.xlarge
      - i3.2xlarge
      - i3.4xlarge
      - i3.8xlarge
      - i3.16xlarge
      - d2.xlarge
      - d2.2xlarge
      - d2.4xlarge
      - d2.8xlarge
      - p2.xlarge
      - p2.8xlarage
      - p2.16xlarge
      - g3.4xlarge
      - g3.8xlarge
      - g3.16xlarge
      - f1.2xlarge
      - f1.16xlarge
      - x1.16xlarge
      - x1.32xlarge
    ConstraintDescription: Must be a valid Amazon EC2 instance type.
    Default: c5.2xlarge
    Description: The Amazon EC2 instance type that adds data to the file system.
    Type: String
  KeyName:
    Description: Name of an existing EC2 key pair
    Type: AWS::EC2::KeyPair::KeyName
  NumberOfSubnets:
    AllowedValues:
    - 1
    - 2
    - 3
    - 4
    - 5
    - 6
    Default: 2
    Description: Number of subnets. This must match your selections in the list of Subnets below.
    Type: String 
  PerformanceMode:
    AllowedValues:
      - generalPurpose
      - maxIO
    Default: generalPurpose
    Description: Select the performance mode of the file system.
    Type: String
  SecurityGroup:
    Description: Select the Amazon EFS security group.
    Type: AWS::EC2::SecurityGroup::Id   
  Subnet:
    Description: Select existing subnets.
    Type: List<AWS::EC2::Subnet::Id>
  Success:
    AllowedValues:
      - "File system creation"
      - "Data load complete"
    Default: "File system creation"
    Description: Select which event signals success.
    Type: String

Conditions:
  NumberOfSubnets1:
      !Equals [ 1, !Ref NumberOfSubnets ]
  NumberOfSubnets2:
      !Equals [ 2, !Ref NumberOfSubnets ]
  NumberOfSubnets3:
      !Equals [ 3, !Ref NumberOfSubnets ]
  NumberOfSubnets4:
      !Equals [ 4, !Ref NumberOfSubnets ]
  NumberOfSubnets5:
      !Equals [ 5, !Ref NumberOfSubnets ]
  NumberOfSubnets6:
      !Equals [ 6, !Ref NumberOfSubnets ]
  UseAWS-ManagedCMK:
    !Equals [ '', !Ref Cmk ]
  Delete:
    !Equals [ !Ref DeletionPolicy, Delete ]
  Retain:
    !Equals [ !Ref DeletionPolicy, Retain ]
  Subnet0: !Or
    - !Condition NumberOfSubnets1
    - !Condition NumberOfSubnets2
    - !Condition NumberOfSubnets3
    - !Condition NumberOfSubnets4
    - !Condition NumberOfSubnets5
    - !Condition NumberOfSubnets6
  Subnet1: !Or
    - !Condition NumberOfSubnets2
    - !Condition NumberOfSubnets3
    - !Condition NumberOfSubnets4
    - !Condition NumberOfSubnets5
    - !Condition NumberOfSubnets6
  Subnet2: !Or
    - !Condition NumberOfSubnets3
    - !Condition NumberOfSubnets4
    - !Condition NumberOfSubnets5
    - !Condition NumberOfSubnets6
  Subnet3: !Or
    - !Condition NumberOfSubnets4
    - !Condition NumberOfSubnets5
    - !Condition NumberOfSubnets6
  Subnet4: !Or
    - !Condition NumberOfSubnets5
    - !Condition NumberOfSubnets6
  Subnet5: !Condition NumberOfSubnets6
  NumberOfSubnets1Delete: !And
    - !Condition Subnet0
    - !Condition Delete
  NumberOfSubnets1Retain: !And
    - !Condition Subnet0
    - !Condition Retain
  NumberOfSubnets2Delete: !And
    - !Condition Subnet1
    - !Condition Delete
  NumberOfSubnets2Retain: !And
    - !Condition Subnet1
    - !Condition Retain
  NumberOfSubnets3Delete: !And
    - !Condition Subnet2
    - !Condition Delete
  NumberOfSubnets3Retain: !And
    - !Condition Subnet2
    - !Condition Retain
  NumberOfSubnets4Delete: !And
    - !Condition Subnet3
    - !Condition Delete
  NumberOfSubnets4Retain: !And
    - !Condition Subnet3
    - !Condition Retain
  NumberOfSubnets5Delete: !And
    - !Condition Subnet4
    - !Condition Delete
  NumberOfSubnets5Retain: !And
    - !Condition Subnet4
    - !Condition Retain
  NumberOfSubnets6Delete: !And
    - !Condition Subnet5
    - !Condition Delete  
  NumberOfSubnets6Retain: !And
    - !Condition Subnet5
    - !Condition Retain
  SuccessFSCreation:
    !Equals [ 'File system creation', !Ref Success ]

Mappings:
  EncrpytionBoolean:
    Encrypted:
      Boolean: true
    Unencrypted:
      Boolean: false
  RegionMap:
    us-east-1:
      AMI: ami-97785bed
    us-east-2:
      AMI: ami-f63b1193
    us-west-2:
      AMI: ami-f2d3638a
    us-west-1:
      AMI: ami-824c4ee2
    ca-central-1:
      AMI: ami-a954d1cd
    eu-west-1:
      AMI: ami-d834aba1
    eu-west-2:
      AMI: ami-403e2524
    eu-west-3:
      AMI: ami-8ee056f3
    eu-central-1:
      AMI: ami-5652ce39
    ap-southeast-1:
      AMI: ami-68097514
    ap-northeast-2:
      AMI: ami-863090e8
    ap-northeast-1:
      AMI: ami-ceafcba8
    ap-southeast-2:
      AMI: ami-942dd1f6
    ap-south-1:
      AMI: ami-531a4c3c
    sa-east-1:
      AMI: ami-84175ae8

Resources:
  ElasticFileSystemRetain:
    Type: AWS::EFS::FileSystem
    Condition: Retain
    DeletionPolicy: Retain
    Properties:
      Encrypted: !FindInMap [ EncrpytionBoolean, !Ref EncryptionState, Boolean ]
      KmsKeyId:
        !If [ UseAWS-ManagedCMK, !Ref 'AWS::NoValue', !Ref Cmk ]
      FileSystemTags:
        - Key: Name
          Value: !Ref 'AWS::StackName'
      PerformanceMode: !Ref PerformanceMode
  ElasticFileSystemDelete:
    Type: AWS::EFS::FileSystem
    Condition: Delete
    DeletionPolicy: Delete
    Properties:
      Encrypted: !FindInMap [ EncrpytionBoolean, !Ref EncryptionState, Boolean ]
      KmsKeyId:
        !If [ UseAWS-ManagedCMK, !Ref 'AWS::NoValue', !Ref Cmk ]
      FileSystemTags:
        - Key: Name
          Value: !Ref 'AWS::StackName'
      PerformanceMode: !Ref PerformanceMode
  ElasticFileSystemMountTarget0Retain:
    Condition: NumberOfSubnets1Retain
    DeletionPolicy: Retain
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemRetain
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 0, !Ref Subnet ]
  ElasticFileSystemMountTarget0Delete:
    Condition: NumberOfSubnets1Delete
    DeletionPolicy: Delete
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemDelete
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 0, !Ref Subnet ]
  ElasticFileSystemMountTarget1Retain:
    Condition: NumberOfSubnets2Retain
    DeletionPolicy: Retain
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemRetain
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 1, !Ref Subnet ]
  ElasticFileSystemMountTarget1Delete:
    Condition : NumberOfSubnets2Delete
    DeletionPolicy: Delete
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemDelete
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 1, !Ref Subnet ]
  ElasticFileSystemMountTarget2Retain:
    Condition: NumberOfSubnets3Retain
    DeletionPolicy: Retain
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemRetain
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 2, !Ref Subnet ]
  ElasticFileSystemMountTarget2Delete:
    Condition: NumberOfSubnets3Delete
    DeletionPolicy: Delete
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemDelete
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 2, !Ref Subnet ]
  ElasticFileSystemMountTarget3Retain:
    Condition: NumberOfSubnets4Retain
    DeletionPolicy: Retain
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemRetain
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 3, !Ref Subnet ]
  ElasticFileSystemMountTarget3Delete:
    Condition: NumberOfSubnets4Delete
    DeletionPolicy: Delete
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemDelete
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 3, !Ref Subnet ]
  ElasticFileSystemMountTarget4Retain:
    Condition: NumberOfSubnets5Retain
    DeletionPolicy: Retain
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemRetain
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 4, !Ref Subnet ]
  ElasticFileSystemMountTarget4Delete:
    Condition: NumberOfSubnets5Delete
    DeletionPolicy: Delete
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemDelete
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 4, !Ref Subnet ]
  ElasticFileSystemMountTarget5Retain:
    Condition: NumberOfSubnets6Retain
    DeletionPolicy: Retain
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemRetain
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 5, !Ref Subnet ]
  ElasticFileSystemMountTarget5Delete:
    Condition: NumberOfSubnets6Delete
    DeletionPolicy: Delete
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref ElasticFileSystemDelete
      SecurityGroups:
      - !Ref SecurityGroup
      SubnetId: !Select [ 5, !Ref Subnet ]
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    DeletionPolicy: Delete
    Properties:
      Path: /
      Roles:
      - !Ref InstanceRole
  InstanceRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      Policies:
      - PolicyName: efs-create-file-system-with-storage
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action:
              - autoscaling:DescribeAutoScalingGroups
              - autoscaling:DescribeAutoScalingInstances
              - autoscaling:DescribePolicies
              - autoscaling:UpdateAutoScalingGroup
            Resource: '*'
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    DeletionPolicy: Delete
    Properties:
      Cooldown: 60
      HealthCheckGracePeriod: 120
      HealthCheckType: EC2
      LaunchConfigurationName: !Ref LaunchConfiguration
      MaxSize: 1
      MinSize: 0
      DesiredCapacity: 1
      Tags:
        - Key: Name
          Value: !Join [ '', [ 'EFS ', !If [ Delete, !Ref ElasticFileSystemDelete, !Ref ElasticFileSystemRetain ], ' data load... will auto terminate.' ] ]
          PropagateAtLaunch: true
      VPCZoneIdentifier:
        !If
          [ NumberOfSubnets1,
          [ !Select [ 0, !Ref Subnet ] ],
          !If
            [ NumberOfSubnets2,
            [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ],
            !If
              [ NumberOfSubnets3,
              [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ],
              !If
                [ NumberOfSubnets4,
                [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ] ],
                !If
                  [ NumberOfSubnets5,
                  [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ] ],
                  [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ], !Select [ 5, !Ref Subnet ] ]
                  ]
                ]
              ]
            ]
          ]
    CreationPolicy:
      ResourceSignal:
        Count: !If [ SuccessFSCreation, 0 , 1 ]
        Timeout: PT12H
    UpdatePolicy:
      AutoScalingReplacingUpdate:
        WillReplace: true
  AutoScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    DeletionPolicy: Delete
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref AutoScalingGroup
      Cooldown: 60
      PolicyType: SimpleScaling
      ScalingAdjustment: 1
  LaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Metadata:
      AWS::CloudFormation::Init:
        configSets:
          efs_add_storage:
            - efs-add-storage
        efs-add-storage:
          files:              
            /tmp/efs-add-storage.sh:
              content: !Sub |
                #!/bin/bash -x

                FILE_SYSTEM_ID=$1
                DATA_DIRECTORY=$2
                GROWTH=$3

                if [ $# -lt 3 ]; then
                  echo "Invalid # of arguments. Require: file system id, data directory, file system growth (GiB) "
                  exit 0
                fi

                # get region from instance meta-data
                availabilityzone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)
                region=${!availabilityzone:0:-1}

                # get instance id
                instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)

                # get autoscaling group name
                asg_name=$(aws autoscaling describe-auto-scaling-instances --instance-ids $instance_id --region $region --output text --query 'AutoScalingInstances[0].AutoScalingGroupName')

                # set the number of threads to the number of vcpus
                threads=$(( $(nproc --all) * 8 ))

                # wait for file system DNS name to be propagated
                results=1
                while [[ $results != 0 ]]; do
                  nslookup $FILE_SYSTEM_ID.efs.$region.amazonaws.com
                  results=$?
                  if [[ results = 1 ]]; then
                    sleep 30
                  fi
                done

                # mount file system
                sudo mkdir -p /$FILE_SYSTEM_ID
                sudo chown ec2-user:ec2-user /$FILE_SYSTEM_ID
                sudo mountpoint -q /$FILE_SYSTEM_ID || sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $FILE_SYSTEM_ID.efs.$region.amazonaws.com:/ /$FILE_SYSTEM_ID

                # create directory if not exists
                sudo mkdir -p /$FILE_SYSTEM_ID/$DATA_DIRECTORY
                sudo chown ec2-user:ec2-user /$FILE_SYSTEM_ID/$DATA_DIRECTORY

                # dd 1GiB files to file system to match DATA_SIZE
                files=$GROWTH
                if [ $(( $files / $threads )) == 0 ];
                  then
                    runs=0
                    parallel_threads=$(( $files % $threads ))
                  else
                    runs=$(( $files / $threads ))
                    parallel_threads=$threads
                fi
                while [ $runs -ge 0 ]; do
                  if [ $runs == 0 ];
                    then
                      parallel_threads=$(( $files % $threads ))
                      seq 0 $(( $parallel_threads - 1 )) | parallel --will-cite -j $parallel_threads --compress dd if=/dev/zero of=/$FILE_SYSTEM_ID/$DATA_DIRECTORY/1G-dd-$(date +%Y%m%d%H%M%S.%3N)-{} bs=1M count=1024 oflag=sync
                      runs=$(($runs-1))
                    else
                      seq 0 $(( $parallel_threads - 1 )) | parallel --will-cite -j $parallel_threads --compress dd if=/dev/zero of=/$FILE_SYSTEM_ID/$DATA_DIRECTORY/1G-dd-$(date +%Y%m%d%H%M%S.%3N)-{} bs=1M count=1024 oflag=sync
                      runs=$(($runs-1))
                  fi
                done

                # set ASG to zero which terminates instance
                aws autoscaling update-auto-scaling-group --auto-scaling-group-name $asg_name --desired-capacity 0 --region $region

              mode: 000777
              owner: root
              group: root             
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            VolumeSize: 10
            VolumeType: gp2
      IamInstanceProfile: !Ref InstanceProfile
      ImageId: !FindInMap [ RegionMap, !Ref 'AWS::Region', AMI ]
      InstanceMonitoring: true
      InstanceType: !Ref InstanceType0
      KeyName: !Ref KeyName
      SecurityGroups:
      - !Ref SecurityGroup
      UserData:
        "Fn::Base64":
          !Join [
            "",[
            "#cloud-config\n",
            "repo_update: true\n",
            "repo_upgrade: all\n",
            "\n",
            "packages:\n",
            "- parallel\n",
            "\n",
            "runcmd:\n",
            "- yum --enablerepo=epel install nload -y\n",
            "- ntpstat\n",
            "- /opt/aws/bin/cfn-init --configsets efs_add_storage --verbose --stack ", !Ref 'AWS::StackName', " --resource LaunchConfiguration --region ", !Ref 'AWS::Region',"\n",
            "- /tmp/efs-add-storage.sh ", !If [ Delete, !Ref ElasticFileSystemDelete, !Ref ElasticFileSystemRetain ], " throughput_data ", !Ref Growth,"\n",
            "- /opt/aws/bin/cfn-signal -e $? --stack ", !Ref 'AWS::StackName', " --resource AutoScalingGroup --region ", !Ref 'AWS::Region',"\n"
            ]
          ]
Outputs:
  ElasticFileSystem:
    Value: !If [ Delete, !Ref ElasticFileSystemDelete, !Ref ElasticFileSystemRetain ]
  ElasticFileSystemDnsName:
    Description: DNS name for the Amazon EFS file system.
    Value: !Join [ '.', [ !If [ Delete, !Ref ElasticFileSystemDelete, !Ref ElasticFileSystemRetain ], 'efs', !Ref 'AWS::Region', 'amazonaws', 'com' ] ]
  ElasticFileSystemMountCommand:
    Description: Mount command for mounting the Amazon EFS file system.
    Value: !Join [ '', [ 'sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 ', !Join [ '.', [ !If [ Delete, !Ref ElasticFileSystemDelete, !Ref ElasticFileSystemRetain ], 'efs', !Ref 'AWS::Region', 'amazonaws', 'com:/', '/', !If [ Delete, !Ref ElasticFileSystemDelete, !Ref ElasticFileSystemRetain ] ] ] ] ]