import test from "ava" ; import { CloudWatchLogs } from "aws-sdk" ; import { v4 as uuid } from "uuid" ; import { faastAws , log , throttle } from "../index" ; import { defaultGcWorker , clearLastGc } from "../src/aws/aws-faast" ; import { getAWSResources } from "./fixtures/util-aws" ; import * as functions from "./fixtures/functions" ; import { checkResourcesCleanedUp , sleep , title } from "./fixtures/util" ; import * as assert from "assert" ; async function waitForLogGroupCreation ( cloudwatch , logGroupName , message ) { let n = 0 ; while ( true ) { await sleep ( 1000 ) ; try { n ++ ; const described = await cloudwatch . describeLogGroups ( { logGroupNamePrefix : logGroupName } ) . promise ( ) ; if ( ! described . logGroups ) { continue ; } const retrievedLogGroup = described . logGroups ?. [ 0 ] ?. logGroupName ; if ( ! retrievedLogGroup ) { continue ; } assert ( retrievedLogGroup === logGroupName , `Unexpected logGroupName: ${ retrievedLogGroup }, expecting ${ logGroupName }` ) ; const logResult = await cloudwatch . filterLogEvents ( { logGroupName } ) . promise ( ) ; const events = logResult . events ?? [ ] ; let foundMessage = false ; for ( const event of events ) { if ( event . message ! . includes ( message ) ) { foundMessage = true ; } if ( foundMessage && event . message ! . match ( /REPORT RequestId/ ) ) { return ; } } } catch ( err ) { console . error ( `Transient error waiting for log group creation: ${ err }` ) ; } } } test . serial ( title ( "aws" , "garbage collects functions that are called" ) , async ( t ) => { const mod = await faastAws ( functions , { gc : "off" , mode : "queue" , description : t . title , packageJson : { name : uuid ( ) , dependencies : { tslib : "^1.9.1" } } , maxRetries : 0 } ) ; try { await mod . functions . consoleLog ( "gc-test-string" ) ; const { cloudwatch } = mod . state . services ; const { logGroupName } = mod . state . resources ; await waitForLogGroupCreation ( cloudwatch , logGroupName , "gc-test-string" ) ; await mod . cleanup ( { deleteResources : false , gcTimeout : 0 } ) ; const deleteRetentionPolicy = throttle ( { concurrency : 1 , retry : 5 } , ( ) => cloudwatch . deleteRetentionPolicy ( { logGroupName } ) . promise ( ) ) ; await deleteRetentionPolicy ( ) ; let deletedLayer = false ; const { layer , FunctionName } = mod . state . resources ; const mod2 = await faastAws ( functions , { gc : "force" , retentionInDays : 0 , description : t . title , _gcWorker : async ( work , services ) => { switch ( work . type ) { case "SetLogRetention" : await defaultGcWorker ( work , services ) ; break ; case "DeleteLayerVersion" : if ( work . LayerName === layer ! . LayerName ) { log . gc ( `deleting layer ${ work . LayerName }` ) ; await defaultGcWorker ( work , services ) ; deletedLayer = true ; } break ; case "DeleteResources" : if ( work . resources . FunctionName === FunctionName ) { log . gc ( `deleting resources for ${ FunctionName }` ) ; await defaultGcWorker ( work , services ) ; } } } } ) ; await mod2 . cleanup ( { gcTimeout : 0 } ) ; t . true ( deletedLayer , "Deleted layer is true" ) ; await checkResourcesCleanedUp ( t , await getAWSResources ( mod , true ) ) ; } finally { log . gc . enabled = false ; await mod . cleanup ( { deleteResources : true , deleteCaches : true , gcTimeout : 0 } ) ; } } ) ; test . serial ( title ( "aws" , "garbage collects functions that are never called" ) , async ( t ) => { const mod = await faastAws ( functions , { gc : "off" , mode : "queue" , description : t . title , maxRetries : 0 } ) ; try { await mod . cleanup ( { deleteResources : false , gcTimeout : 0 } ) ; const { FunctionName } = mod . state . resources ; const mod2 = await faastAws ( functions , { gc : "force" , retentionInDays : 0 , description : t . title , _gcWorker : async ( work , services ) => { switch ( work . type ) { case "DeleteResources" : if ( work . resources . FunctionName === FunctionName ) { log . gc ( `deleting resources for ${ FunctionName }` ) ; await defaultGcWorker ( work , services ) ; } } } } ) ; await mod2 . cleanup ( { gcTimeout : 0 } ) ; const { logGroupResult , ... resources } = await getAWSResources ( mod , true ) ; await checkResourcesCleanedUp ( t , resources ) ; } finally { await mod . cleanup ( { deleteResources : true , deleteCaches : true , gcTimeout : 0 } ) ; } } ) ; test . skip ( title ( "aws" , "garbage collection caching" ) , async ( t ) => { { const mod = await faastAws ( functions , { gc : "force" , description : t . title } ) ; await mod . cleanup ( { gcTimeout : 0 } ) ; t . is ( await mod . state . gcPromise , "done" ) ; } { const mod = await faastAws ( functions , { description : t . title } ) ; await mod . cleanup ( ) ; t . is ( await mod . state . gcPromise , "skipped" ) ; } { clearLastGc ( ) ; const mod = await faastAws ( functions , { description : t . title } ) ; await mod . cleanup ( ) ; t . is ( await mod . state . gcPromise , "skipped" ) ; } } ) ;