Search This Blog

Sunday, 26 April 2020

Triggering Lambda on Schedule

I had a requirement recently where I needed to run a Monitoring code hourly to gather and plot some stats. With monitor duration of 1 second and total executions of 24/day, serverless seemed to be the way to go. However Serverless being an event driven world, I needed some way to trigger my Lambda.
For these kind of use cases, AWS provides Cloud Watch Events. From the docs
Amazon CloudWatch Events delivers a near real-time stream of 
system events that describe changes in Amazon Web Services (AWS) resources.
What I needed was a new event triggered on a schedule. Consider the simple Lambda I setup for this exercise
public class LoggerLambda implements RequestHandler<ScheduledEvent, Void> {

    @Override
    public Void handleRequest(ScheduledEvent input, Context context) {
        LambdaLogger logger = context.getLogger();
        logger.log("In Handler: Executing " + context.getFunctionName() + ", " + context.getFunctionVersion());
        logger.log(input.toString()); //This input is not really used now
        return null;
    }
}
The code receives a ScheduledEvent instance, which represents a scheduled CloudWatch Event. It simply logs the event.
To trigger this event, I setup a simple Rule
The rule is to trigger the LambdaFunction I created every minute. The Cloud Watch Event received as input was used as is:

The execution logs in Cloud Watch are:
START RequestId: b6695a38-50da-4b56-89e5-4a4c4755ce3a Version: $LATEST
In Handler: Executing ScheduledLambda, $LATEST
{account: 083144642440,region: us-east-1,detail: {},detailType: Scheduled Event,source:
 aws.events,id: f9ca55df-c7e2-3094-6f2d-1a2a267ec033,time: 2020-04-25T17:31:42.000Z,resources:
 [arn:aws:events:us-east-1:083144642440:rule/TestLambdaWithDefaultEvent]}
END RequestId: b6695a38-50da-4b56-89e5-4a4c4755ce3a
As seen the ScheduledEvent object includes all fields we saw on the CloudWatch Event console.  decided to just pass part of the matched Event instead.
On running, the lambda failed:
START RequestId: 7dcdb189-1bc1-4344-be8b-bc3690b7295c Version: $LATEST
An error occurred during JSON parsing: java.lang.RuntimeException
java.lang.RuntimeException: An error occurred during JSON parsing
Caused by: java.io.UncheckedIOException: com.fasterxml.jackson.databind.JsonMappingException: 
Can not instantiate value of type [simple type, class com.amazonaws.services.lambda.runtime.events.ScheduledEvent]
 from String value ('0'); no single-String constructor/factory method
 at [Source: lambdainternal.util.NativeMemoryAsInputStream@56aac163; line: 1, column: 1]
Caused by: com.fasterxml.jackson.databind.JsonMappingException: 
Can not instantiate value of type [simple type, class com.amazonaws.services.lambda.runtime.events.ScheduledEvent]
 from String value ('0'); no single-String constructor/factory method
 at [Source: lambdainternal.util.NativeMemoryAsInputStream@56aac163; line: 1, column: 1]
 at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
 at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:878)
 at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:281)
 at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:284)
 at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1176)
 at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:145)
 at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:136)
 at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1511)
 at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1102)
The $version when sent as input to my Lambda, it cannot be parsed into a ScheduledEvent. On updating the code
public class LoggerLambda implements RequestHandler<String, Void> {

    @Override
    public Void handleRequest(String input, Context context) {
        LambdaLogger logger = context.getLogger();
        logger.log("In Handler: Executing " + context.getFunctionName() + ", " + context.getFunctionVersion());
        logger.log(input); //This input is not really used now
        return null;
    }
}
This worked as the String input could be loaded into my Lambda:
START RequestId: f6f421cc-9507-4dc0-8d30-455e040ada5a Version: $LATEST
In Handler: Executing ScheduledLambda, $LATEST
0
END RequestId: f6f421cc-9507-4dc0-8d30-455e040ada5a
REPORT RequestId: f6f421cc-9507-4dc0-8d30-455e040ada5a Duration: 15.22 ms 
Billed Duration: 100 ms Memory Size: 512 MB Max Memory Used: 75 MB Init Duration: 309.39 ms
Similarly if we wanted to pass a constant (has to be Json form)

I updated my Lambda accordingly
public class LoggerLambda implements RequestHandler<ComputeEntity, Void> {

    @Override
    public Void handleRequest(ComputeEntity input, Context context) {
        LambdaLogger logger = context.getLogger();
        logger.log("In Handler: Executing " + context.getFunctionName() + ", " + context.getFunctionVersion());
        logger.log(input.toString());
        return null;
    }
}
The json class
public class ComputeEntity {
    private String type;
    private Entity entity;

    public enum Entity {
        Employee,
        Supervisor,
        Customer
    }
    
    @Override
    public String toString() {
        return "ComputeEntity{" + "type='" + type + '\'' + ", entity=" + entity + '}';
    }
    //getter setters
}
On running this, Lambda converted my static Json into the POJO class object while executing my function:
START RequestId: b8404982-3c03-46fa-b755-5d67bae53f9e Version: $LATEST
In Handler: Executing ScheduledLambda, $LATEST
ComputeEntity{type='computeArrears', entity=Employee}
END RequestId: b8404982-3c03-46fa-b755-5d67bae53f9e
REPORT RequestId: b8404982-3c03-46fa-b755-5d67bae53f9e Duration: 72.24 ms 
Billed Duration: 100 ms Memory Size: 512 MB Max Memory Used: 79 MB Init Duration: 351.94 ms
The last option available is Input Transformer which allows you to build a String input using details from the Event

I updated the Lambda function to take string as input
The result is
START RequestId: 9847937c-bf57-477e-a29c-2909ee8db831 Version: $LATEST
In Handler: Executing ScheduledLambda, $LATEST
The time of event is 2020-04-25T19:58:27Z and id is 9956c6df-64ea-6a6a-0812-61ad6e829218
END RequestId: 9847937c-bf57-477e-a29c-2909ee8db831
REPORT RequestId: 9847937c-bf57-477e-a29c-2909ee8db831 Duration: 26.95 ms 
Billed Duration: 100 ms Memory Size: 512 MB Max Memory Used: 76 MB Init Duration: 325.88 ms 
I was interested if we could add stuff into the original Event. For example the details field is blank for custom events, however that is not allowed. We could:

  • either pass the Event as is
  • send part of the  Scheduled Event forward
  • Send a constant JSON message
  • Send a custom String message built from the Event

1 comment:

  1. Well research and resourceful content indeed. Also visit here: Feeta.pk apartments for sale in Islamabad . Thank you for this useful material! It’s amazing!

    ReplyDelete