Async Lambda invoke from another Lambda

aws

#1

I need help triggering the start of a Lambda function from a different Lambda function. It would be nice to know if the triggering was successful or not, but not required. I have made many unsuccessful attempts to accomplish this. I’m hoping that the community can help me out.

So far I have done the following.

  • Created a role manually in AWS and assign
  • Add extra permissions to the provider section in the serverless.yml file
  • Call invoke with InvocationType set to Event
  • Call invokeAsync
  • Call invoke without a callback and manually call send

What does it mean to fail?

I’ve set the test function’s run time to be 30 seconds. All runs show the standard 3 log messages for start, end, and report. In between the start and end messages I should see a pair of messages before and after the call to invoke. Failure is happening because I only get the before invoke log message.

Details

My test function is the following

export const handler = (event, context, callback) => {
  console.log('event', JSON.stringify(event, true));
  console.log('context', JSON.stringify(context, true));
  const functionName = 'the-other-function; // this exists
  const payload = { foo: 'bar' };

  console.log('before invoke');
  promiseInvoke({ functionName, payload })
    .then(() => {
      console.log('after invoke');
      callback(null, null);
    })
    .catch(error => {
      console.error(error);
      callback(error);
    });
  console.log('handler exit');
};
  • Created a role manually in AWS and assign

I created a IAM role manually and assigned admin level privileges. Then for the test function I set its role property in the serverless.yml file. This failed in the same way as the other attempts.

  • Add extra permissions to the provider section in the serverless.yml file

I followed the results of forum post. I also needed to connect to a DB so I included some ec2 permissions to connect to the network. This failed in the same way as the other attempts.

provider:
  name: aws
  iamRoleStatements:
    - Effect: 'Allow'
      Action:
        - "logs:CreateLogGroup"
        - "logs:CreateLogStream"
        - "logs:PutLogEvents"
        - "ec2:CreateNetworkInterface"
        - "ec2:DescribeNetworkInterfaces"
        - "ec2:DeleteNetworkInterface"
        - "lambda:InvokeFunction"
        - "lambda:InvokeAsync"
      Resource: "*"
  • Call invoke with InvocationType set to Event
  • Call invokeAsync
  • Call invoke without a callback and manually call send

I created several methods of calling the invoke method from the aws-sdk for JavaScript. I put these in a Gist on Github. The AWS docs say to use the invoke method since the invokeAsync method is deprecated. Even so, I tried that method too.

The basic method was the following. This failed in the same way as the other attempts.

export const promiseInvoke = ({ functionName, payload }) => {
  console.log('Starting promiseInvoke with native promise');
  const lambda = new AWS.Lambda();
  return lambda.invoke({
    InvocationType: 'Event',
    FunctionName: functionName,
    LogType: 'None',
    Payload: JSON.stringify(payload)
  }).promise();
};

#2

Just to be clear - is your target function failing after hitting your timeout (i.e. 30 seconds) or is there an error message?

I’m a bit confused by the ordering of your steps - are you giving permissions to your function by adding them to the iamRoleStatements property in the provider section, OR are you creating an IAM and manually assigning it to your function?

Your invoke function looks good, the only thing I would do is move the const lambda = new AWS.Lambda(); outside of your function - there’s no need to initialise the object on every invocation, you can do it once per container by putting it in the module’s top-level scope.


#3

No error message at all. Just the system start message, my pre-invoke call message, then the system end and report messages.

I assume a timeout because the duration is always the run time I set for the function.[quote=“rowanu, post:2, topic:1417”]
I’m a bit confused by the ordering of your steps - are you giving permissions to your function by adding them to the iamRoleStatements property in the provider section, OR are you creating an IAM and manually assigning it to your function?
[/quote]

Both. I tried every combo I could think of. With the provider added permissions. Without those, but with a manually made role. With a role and the provider extra permissions in the same serverless.yml file. I even tried without lambda permissions hoping to get an error message.


#4

Output looks like this

{
    "errorMessage": "2017-03-01T22:30:46.085Z 9c68f19d-fece-11e6-8aa8-3381dd02f4c9 Task timed out after 30.00 seconds"
}
--------------------------------------------------------------------
START RequestId: 9c68f19d-fece-11e6-8aa8-3381dd02f4c9 Version: $LATEST
2017-03-01 22:30:16.642 (+00:00)        9c68f19d-fece-11e6-8aa8-3381dd02f4c9    Starting promiseInvoke with native promise
END RequestId: 9c68f19d-fece-11e6-8aa8-3381dd02f4c9
REPORT RequestId: 9c68f19d-fece-11e6-8aa8-3381dd02f4c9  Duration: 30003.34 ms   Billed Duration: 30000 ms       Memory Size: 128 MB     Max Memory Used: 29 MB
2017-03-01T22:30:46.085Z 9c68f19d-fece-11e6-8aa8-3381dd02f4c9 Task timed out after 30.00 seconds


#5

Yep, definitely a timeout (you can see the message in the last line).

Can you try setting the InvocationType to DryRun and re-run it?
This will not actually call the function, but it will confirm you have the right permissions to call it (as per the docs):

You can also use this parameter to request AWS Lambda to not execute the function but do some verification, such as if the caller is authorized to invoke the function and if the inputs are valid. You request this by specifying DryRun as the InvocationType.


#6

DryRun produces the same output. Or, lack there of, really.

An error message would be a wonderful thing.


#7

Very strange - you should get something from the DryRun setting!

Maybe it’s not even getting to the lambda.invoke - can you move the const lambda declaration out of the function (the line before it should be fine). If the function was called multiple times at once, the reinitialisation could be an issue…


#8

I’ve updated the code and deployed. Same issue. The Gist got updated to, so you can look at the code.


#9

There’s nothing obviously wrong I can see.

Have you tried logging out the function name and payload, to make sure they’re as you expect? Obviously the function name needs to exist, and the payload should be a valid JSON object (but I would’ve expected all kinds of errors if the weren’t both the case already).


#10

I’m having exactly the same problem. The frist lambda gets a timeout (no error is visible in the log). Both lambda functions them self runs with no errors and successfully.
I’m not sure if the second lambda is even called, there is nothing in the CloudWatch log. But the invoke code is executed in my code but nothing happend.

Can somebody provide a minimal example for this use case or give me some tipps?


#11

To invoke the lambda function via sdk the lambda functions need to have internet access. Follow the following instruction to get it done: https://gist.github.com/reggi/dc5f2620b7b4f515e68e46255ac042a7


#12

I am experiencing the same issue. Any updates on the above problem?


#13

For me the issue was subnet routes. It was a silent error of not being able to reach the IP address of the Amazon service and the protected resource my lambda was using.

Check your routes to the service your calling from lambda. Then check the Amazon gateway for external accessible services.


#14

I think the issue here is that your Lambda function needs a resource policy that allows its invocation from your test function.

Unfortunately, adding "InvokeFunction", "InvokeAsync" privileges to your Lambda role will not work. This is essentially saying your Lambda can invoke other Lambdas.

It’s easy to see in the AWS console if your Lambda function can be invoked by other Lambdas/whomever by going into the Lambda service page, picking your function, going to the Triggers tab and expanding the View function policy drop down.

I’m on the same boat as you, looking for a way to assign invocation permissions from the Serverless configuration, but I’m coming up short. But not all is lost! You can either add it manually in the console, or using the AWS CLI by using the add-permission functionality: http://docs.aws.amazon.com/cli/latest/reference/lambda/add-permission.html

I’m curious if adding permissions to invoke your Lambda to your test function’s app role policy would work.

Edit:

To add to all this I’ll say that, generally speaking, you don’t want to invoke a Lambda from another (unless you have a good reason, which in my case may be true) because debugging integration issues between the two becomes more cumbersome. Refer to this excellent conversation for more info: Best Practice Question: API call, Lambda.Invoke, or require()?


Allow any lambda to invoke lambda with resource policy?
#16

@Catertion please don’t necro-bump this thread with new questions - create a new topic for them! Topics are free!


#17

Did anyone ever figure this out?

If I use AWS SDK to ‘invoke’ on Lambda function from another it works fine. HOWEVER, if I try to do this asynchronously, like this, the 2nd function call, ‘myOtherLambda’, never gets called, no errors, just gets bypassed as if it were not there.

exports.handler = async (event) => {

const dataDict = await parseLeads(event);
const esResponse  = await myOtherLambda(dataDict);

};


#18

I whipped up a simple example:
serverless.yml

service: simple-service

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage} # force required as deployment param eg. -s dev
  region: ${opt:region, 'us-west-2'} # default us-west-2, override with eg. -r us-east-1

  iamRoleStatements:
    - Effect: Allow
      Action:
        - 'lambda:InvokeFunction'
      Resource: '*'

functions:
  test:
    handler: handlers.test
    environment:
      Lambda2:
        Ref: Test2LambdaFunction
  test2:
    handler: handlers.test2

handlers.js

'use strict'

const { Lambda } = require('aws-sdk')

/**
 * test Lambda function
 */
module.exports.test = async (event, context) => {
  console.log(`test.handler - event: ${JSON.stringify(event, null, 2)}`)

  console.log(`before calling test2(${process.env.Lambda2})`)
  try {
    let res = await new Lambda().invoke({
      FunctionName: process.env.Lambda2,
      Payload: JSON.stringify(event),
      InvocationType: 'Event' // <-- This is the key to being Async If you need the response use RequestResponse
    }).promise()

    console.log(`invoke test2 response: ${JSON.stringify(res, null, 2)}`)
  } catch (err) {
    console.log(`invoke ERROR: ${err}`)
  }
  console.log(`after calling test2`)
}

/**
 * test2 Lambda function
 */
module.exports.test2 = async (event, context) => {
  console.log(`test2.handler - event: ${JSON.stringify(event, null, 2)}`)
}

deploy

sls deploy -s whatever

invoke test

sls invoke -s whatever -f test -l

test output

$ sls invoke -s whatever -f test -l
null
--------------------------------------------------------------------
START RequestId: 7d590b98-f418-11e8-aea5-0f197cdb6bd9 Version: $LATEST
2018-11-29 12:51:05.849 (-08:00)        7d590b98-f418-11e8-aea5-0f197cdb6bd9    test.handler - event: {}
2018-11-29 12:51:05.849 (-08:00)        7d590b98-f418-11e8-aea5-0f197cdb6bd9    before calling test2(simple-service-whatever-test2)
2018-11-29 12:51:06.003 (-08:00)        7d590b98-f418-11e8-aea5-0f197cdb6bd9    invoke test2 response: {
  "StatusCode": 202,
  "Payload": ""
}
2018-11-29 12:51:06.003 (-08:00)        7d590b98-f418-11e8-aea5-0f197cdb6bd9    after calling test2
END RequestId: 7d590b98-f418-11e8-aea5-0f197cdb6bd9
REPORT RequestId: 7d590b98-f418-11e8-aea5-0f197cdb6bd9  Duration: 156.14 ms     Billed Duration: 200 ms         Memory Size: 1024 MB    Max Memory Used: 30 MB

If you want to view the logs from the test2 function:

sls logs -s whatever -f test2 -t

Hope this helps.


#19

Am I correct to understand that Lambda’s, even when in the same VPC and subnet, will need internet access to invoke eachother directly?