 # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 # SPDX-License-Identifier: MIT-0
 #
 # 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.
 #
 # 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.
 
AWSTemplateFormatVersion: 2010-09-09
Description: Dynamically scale an Amazon FSx for OpenZFS file system based on CloudWatch Alarm

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: Dynamic Storage Scaling Parameters
      Parameters:
        - FileSystemId
        - UsedStorageCapacity   
        - PercentIncrease     
        - EmailAddress
        - MaxFSxSizeinGiB
        
    ParameterLabels:
      EmailAddress:
        default: Email address
      FileSystemId:
        default: File system ID
      MaxFSxSizeinGiB:
        default: Maximum supported file system storage capacity (DO NOT MODIFY)
      PercentIncrease:
        default: Percentage Capacity increase
      UsedStorageCapacity:
        default: Threshold

Parameters:
  EmailAddress:
    Description: The email address for alarm notification.
    Type: String
  FileSystemId:
    AllowedPattern: ^(fs-)([a-z0-9]{17})$
    Description: Amazon FSx file system ID
    Type: String
  UsedStorageCapacity:
    ConstraintDescription: Must be an integer.
    Description: Used storage capacity threshold (%)
    Type: Number
  PercentIncrease:
    Description: The percentage increase in storage capacity when used storage exceeds UsedStorageCapacity. Minimum increase is 10 %
    Type: Number
    Default: 20
    MaxValue: 100
    MinValue: 10
  MaxFSxSizeinGiB:
    Description: Maximum size supported for the storage capacity.
    Default: 524288
    Type: Number
    MaxValue: 524288
    MinValue: 100

Resources:
  FSxOpenZFSStorageScaler:
    Type: AWS::Lambda::Function
    Properties:
        FunctionName: !Sub ${AWS::StackName}
        Description: Amazon FSx for OpenZFS dynamic storage scaling function
        Environment:
          Variables:
            fileSystemId: !Ref FileSystemId
            snsTopicArn: !Sub ${SNSTopic}
            maxFSxOpenZFSSizeinGiB: !Ref MaxFSxSizeinGiB
            percentIncrease: !Ref PercentIncrease
        Handler: lambda_function.lambda_handler
        Role: !Sub ${FSxOpenZFSStorageScalerRole.Arn}
        Code:
           S3Bucket: !Sub solution-references-${AWS::Region}
           S3Key: 'fsx/DynamicScaling/FSxZStack.zip'
        Runtime: python3.9
        Timeout: 300

  FSxOpenZFSStorageScalerLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Sub ${FSxOpenZFSStorageScaler}
      Action: lambda:InvokeFunction
      Principal: sns.amazonaws.com
      SourceArn: !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${SNSTopic.TopicName}

  FSxOpenZFSStorageScalerRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub FSxOpenZFSStorageScaler-Role-${AWS::StackName}-${AWS::Region}
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AWSLambdaExecute'
      Path: /
      Policies:
      - PolicyName: !Sub FSxOpenZFSStorageScaler-Policy-${AWS::StackName}-${AWS::Region}
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - logs:CreateLogGroup
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
          - Effect: Allow
            Action:
            - fsx:UpdateFileSystem
            - fsx:DescribeFileSystems
            Resource: "*"
          - Effect: Allow
            Action:
            - sns:Publish
            Resource: !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${FileSystemId}-storage-capacity-alarm-notification
          - Effect: Allow
            Action:
            - cloudwatch:SetAlarmState
            Resource: !Sub arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:Low free storage capacity alarm - ${FileSystemId}

  SNSTopic: 
    Type: AWS::SNS::Topic
    Properties: 
      DisplayName: !Join [ '', [ !Ref FileSystemId, '-storage-capacity-alarm-notification' ] ]
      Subscription:
      - Endpoint: !Ref EmailAddress
        Protocol: "email"
      - Endpoint: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${AWS::StackName}
        Protocol: lambda
      TopicName: !Join [ '', [ !Ref FileSystemId, '-storage-capacity-alarm-notification' ] ]

  LowFreeDataStorageCapacityAlarm:
    DependsOn: SNSTopic
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
      - !Sub ${SNSTopic}
      AlarmDescription: !Join [ '', [ 'Low free storage capacity alarm - ',!Ref FileSystemId ] ]
      AlarmName: !Join [ '', [ 'Low free storage capacity alarm - ',!Ref FileSystemId ] ]
      ComparisonOperator: GreaterThanThreshold
      EvaluationPeriods: 5
      Metrics: 
      - Expression: "100*(m2/m1)"
        Id: "e1"
        Label: PercentageStorageUsed
        ReturnData: true
      - Id: "m1"
        Label: StorageCapacity
        MetricStat:
          Metric:
            Dimensions:
              - Name: FileSystemId
                Value: !Ref FileSystemId
            MetricName: StorageCapacity
            Namespace: AWS/FSx
          Period: 60
          Stat: Maximum
        ReturnData: false
      - Id: "m2"
        Label: StorageUsed
        MetricStat:
          Metric:
            Dimensions:
              - Name: FileSystemId
                Value: !Ref FileSystemId
            MetricName: UsedStorageCapacity
            Namespace: AWS/FSx
          Period: 60
          Stat: Average
        ReturnData: false
      Threshold: !Ref UsedStorageCapacity
      TreatMissingData: missing