/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */ import { InstanceType , IVpc , SubnetSelection , } from '@aws-cdk/aws-ec2' ; import * as cdk from '@aws-cdk/core' ; import { ComparisonOperator , Metric , TreatMissingData , } from '@aws-cdk/aws-cloudwatch' ; import { SnsAction , } from '@aws-cdk/aws-cloudwatch-actions' ; import { DatabaseCluster } from '@aws-cdk/aws-docdb' ; import { AccessPoint , FileSystem , } from '@aws-cdk/aws-efs' ; import { ServicePrincipal , } from '@aws-cdk/aws-iam' ; import { Key , } from '@aws-cdk/aws-kms' ; import { IPrivateHostedZone } from '@aws-cdk/aws-route53' ; import { Topic , } from '@aws-cdk/aws-sns' ; import { EmailSubscription , } from '@aws-cdk/aws-sns-subscriptions' ; import { RemovalPolicy , Duration } from '@aws-cdk/core' ; import { MongoDbInstance , MongoDbPostInstallSetup , MongoDbSsplLicenseAcceptance , MongoDbVersion , MountableEfs , PadEfsStorage , X509CertificatePem , X509CertificatePkcs12 , } from 'aws-rfdk' ; import { DatabaseConnection , } from 'aws-rfdk/deadline' ; import { Subnets } from './subnets' ; /**
 * Properties for  StorageTier .
  export interface StorageTierProps extends cdk . StackProps { /**
     * The VPC to deploy resources into.
     */ readonly vpc : IVpc ; default readonly alarmEmail ? : string ; } /**
 * The storage tier of the render farm. This stack contains all constructs that persist
 * data which would be useful to keep between deployments. There should little to no "business-logic"
 * constructs in this stack.
 */ export abstract class StorageTier extends cdk . Stack { /**
     * The mountable file-system to use for the Deadline Repository
     */ public readonly mountableFileSystem : MountableEfs ; /**
     * The database to connect Deadline to.
     */ public abstract readonly database : DatabaseConnection ; /**
     * Initializes a new instance of  StorageTier .
     *  param scope param id param props constructor ( scope , id , props ) { super ( scope , id , props ) ; const fileSystem = new FileSystem ( this , 'EfsFileSystem' , { vpc : props . vpc , vpcSubnets : { subnetGroupName : Subnets . INFRASTRUCTURE . name , } , encrypted : true , removalPolicy : RemovalPolicy . DESTROY , } ) ; const accessPoint = new AccessPoint ( this , 'AccessPoint' , { fileSystem , createAcl : { ownerGid : '10000' , ownerUid : '10000' , permissions : '750' , } , path : '/DeadlineRepository' , posixUser : { uid : '10000' , gid : '10000' , } , } ) ; this . mountableFileSystem = new MountableEfs ( this , { filesystem : fileSystem , accessPoint , extraMountOptions : [ 'fsc' ] } ) ; if ( props . alarmEmail ) { this . addLowEfsBurstCreditAlarms ( fileSystem , props . alarmEmail ) ; } const padAccessPoint = new AccessPoint ( this , 'PaddingAccessPoint' , { fileSystem , path : '/RFDK_PaddingFiles' , createAcl : { ownerGid : '0' , ownerUid : '0' , permissions : '700' , } , posixUser : { uid : '0' , gid : '0' , } , } ) ; new PadEfsStorage ( this , 'PadEfsStorage' , { vpc : props . vpc , vpcSubnets : { subnetGroupName : Subnets . INFRASTRUCTURE . name , } , accessPoint : padAccessPoint , desiredPadding : cdk . Size . gibibytes ( 40 ) , } ) ; } /**
     * Set up CloudWatch Alarms that will warn when the given filesystem's burst credits are below
     * four different thresholds. We send an email to the given address when an Alarm breaches.
     */ protected addLowEfsBurstCreditAlarms ( filesystem , emailAddress ) { const key = new Key ( this , 'SNSEncryptionKey' , { description : 'Used to encrypt the SNS Topic for sending EFS Burst Credit alerts' , enableKeyRotation : true , removalPolicy : RemovalPolicy . DESTROY , trustAccountIdentities : true , } ) ; key . grant ( new ServicePrincipal ( 'cloudwatch.amazonaws.com' ) , 'kms:Decrypt' , 'kms:GenerateDataKey' ) ; const snsTopic = new Topic ( this , 'BurstAlertEmailTopic' , { masterKey : key , } ) ; snsTopic . grantPublish ( new ServicePrincipal ( 'cloudwatch.amazonaws.com' ) ) ; snsTopic . addSubscription ( new EmailSubscription ( emailAddress ) ) ; const alarmAction = new SnsAction ( snsTopic ) ; const burstCreditsMetric = new Metric ( { metricName : 'BurstCreditBalance' , namespace : 'AWS/EFS' , dimensionsMap : { FileSystemId : filesystem . fileSystemId , } , period : Duration . hours ( 1 ) , statistic : 'p99' , } ) ; const thresholds = [ { id : 'CAUTION-EfsBurstCredits' , name : `CAUTION Burst Credits - ${ filesystem . fileSystemId }` , threshold : 2.00 * 2 ** 40 , message : `CAUTION. 2 TiB Threshold Breached: EFS ${ filesystem . fileSystemId } is depleting burst credits. Add data to the EFS to increase baseline throughput.` , datapoints : 6 } , { id : 'WARNING-EfsBurstCredits' , name : `WARNING Burst Credits - ${ filesystem . fileSystemId }` , threshold : 1.25 * 2 ** 40 , message : `WARNING. 1.25 TiB Threshold Breached: EFS ${ filesystem . fileSystemId } is depleting burst credits. Add data to the EFS to increase baseline throughput.` , datapoints : 6 } , { id : 'ALERT-EfsBurstCredits' , name : `ALERT Burst Credits - ${ filesystem . fileSystemId }` , threshold : 0.50 * 2 ** 40 , message : `ALERT! 500 GiB Threshold Breached: EFS ${ filesystem . fileSystemId } is running out of burst credits. Add data to the EFS to increase baseline throughput or else the Render Farm may cease operation.` , datapoints : 6 } , { id : 'EMERGENCY-EfsBurstCredits' , name : `EMERGENCY Burst Credits - ${ filesystem . fileSystemId }` , threshold : 0.10 * 2 ** 40 , message : `EMERGENCY! 100 GiB Threshold Breached: EFS ${ filesystem . fileSystemId } is running out of burst credits. Add data to the EFS to increase baseline throughput or else the Render Farm will cease operation.` , datapoints : 2 } , ] ; for ( let config of thresholds ) { const alarm = burstCreditsMetric . createAlarm ( this , config . id , { alarmName : config . name , actionsEnabled : true , alarmDescription : config . message , treatMissingData : TreatMissingData . NOT_BREACHING , threshold : config . threshold , comparisonOperator : ComparisonOperator . LESS_THAN_THRESHOLD , evaluationPeriods : config . datapoints , } ) ; alarm . addAlarmAction ( alarmAction ) ; } } } /**
 * Properties for  StorageTierDocDB .
  export interface StorageTierDocDBProps extends StorageTierProps { /**
     * The  InstanceType for DocDB.
      readonly databaseInstanceType : InstanceType ; } /**
 * An implementation of  StorageTier that is backed by DocumentDB.
  export class StorageTierDocDB extends StorageTier { /**
     * The DocDB connection.
     */ public readonly database : DatabaseConnection ; /**
     * Initiailizes a new instance of  StorageTierDocDB .
     *  param scope param id param props constructor ( scope , id , props ) { super ( scope , id , props ) ; const docDb = new DatabaseCluster ( this , 'DocDBCluster' , { vpc : props . vpc , vpcSubnets : { subnetGroupName : Subnets . INFRASTRUCTURE . name , } , instanceType : props . databaseInstanceType , instances : 1 , masterUser : { username : 'adminuser' , } , engineVersion : '3.6.0' , backup : { retention : Duration . days ( 15 ) , } , removalPolicy : RemovalPolicy . DESTROY , } ) ; this . database = DatabaseConnection . forDocDB ( { database : docDb , login : docDb . secret ! , } ) ; } } /**
 * Properties for  StorageTierMongoDB .
  export interface StorageTierMongoDBProps extends StorageTierProps { /**
     * The  InstanceType for MongoDB.
      readonly databaseInstanceType : InstanceType ; /**
     * Self-signed root CA to sign server certificates with.
     */ readonly rootCa : X509CertificatePem ; /**
     * Internal DNS zone for the VPC.
     */ readonly dnsZone : IPrivateHostedZone ; /**
     * Whether the SSPL license is accepted or not.
     */ readonly acceptSsplLicense : MongoDbSsplLicenseAcceptance ; /**
     * The name of the EC2 keypair to associate with the MongoDB instance.
     */ readonly keyPairName ? : string ; } /**
 * An implementation of  StorageTier that is backed by MongoDB.
  export class StorageTierMongoDB extends StorageTier { /**
     * The MongoDB connection.
     */ public readonly database : DatabaseConnection ; /**
     * Initiailizes a new instance of  StorageTierMongoDB .
     *  param scope param id param props constructor ( scope , id , props ) { super ( scope , id , props ) ; const serverCert = new X509CertificatePem ( this , 'MongoCert' , { subject : { cn : `mongo.${ props . dnsZone . zoneName }` , o : 'RFDK-Sample' , ou : 'MongoServer' , } , signingCertificate : props . rootCa , } ) ; const clientCert = new X509CertificatePem ( this , 'DeadlineMongoCert' , { subject : { cn : 'SampleUser' , o : 'RFDK-Sample' , ou : 'MongoClient' , } , signingCertificate : props . rootCa , } ) ; const clientPkcs12 = new X509CertificatePkcs12 ( this , 'DeadlineMongoPkcs12' , { sourceCertificate : clientCert , } ) ; const availabilityZone = props . vpc . availabilityZones [ 0 ] ; const mongoSubnets = { subnetGroupName : Subnets . INFRASTRUCTURE . name , availabilityZones : [ availabilityZone ] } ; const mongoDb = new MongoDbInstance ( this , 'MongoDb' , { vpc : props . vpc , vpcSubnets : mongoSubnets , keyName : props . keyPairName , instanceType : props . databaseInstanceType , mongoDb : { userSsplAcceptance : props . acceptSsplLicense , version : MongoDbVersion . COMMUNITY_3_6 , hostname : 'mongo' , dnsZone : props . dnsZone , serverCertificate : serverCert , } , } ) ; new MongoDbPostInstallSetup ( this , 'MongoDbPostInstall' , { vpc : props . vpc , vpcSubnets : mongoSubnets , mongoDb , users : { x509AuthUsers : [ { certificate : clientCert . cert , roles : JSON . stringify ( [ { role : 'readWriteAnyDatabase' , db : 'admin' } , { role : 'clusterMonitor' , db : 'admin' } ] ) , } , ] , } , } ) ; this . database = DatabaseConnection . forMongoDbInstance ( { database : mongoDb , clientCertificate : clientPkcs12 , } ) ; } }