Skip to main content

Lab: Creating and Testing Lambda Functions

Lab Objective​

Create, configure, and test Lambda functions with different triggers. This lab uses LocalStack to simulate the AWS environment.

Part 1: Hello World Function​

Exercise 1.1: Create the Function​

Code to create: hello_world.py

import json
from datetime import datetime

def lambda_handler(event, context):
"""
Hello World function with logging
"""
# Log the received event
print(f"Event received: {json.dumps(event)}")
print(f"Context: {context.function_name}, {context.aws_request_id}")

# Extract the name
name = event.get('name', 'World')
timestamp = datetime.now().isoformat()

# Build the response
response = {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({
'message': f'Hello, {name}!',
'timestamp': timestamp,
'function': context.function_name
})
}

print(f"Response: {json.dumps(response)}")
return response

Exercise 1.2: LocalStack Deployment​

# 1. Start LocalStack (if not already done)
docker-compose up -d

# 2. Create the IAM role
aws --endpoint-url=http://localhost:4566 iam create-role \
--role-name lambda-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'

# 3. Zip the code
zip hello_world.zip hello_world.py

# 4. Create the function
aws --endpoint-url=http://localhost:4566 lambda create-function \
--function-name hello-world \
--runtime python3.9 \
--role arn:aws:iam::000000000000:role/lambda-role \
--handler hello_world.lambda_handler \
--zip-file fileb://hello_world.zip

# 5. Test
aws --endpoint-url=http://localhost:4566 lambda invoke \
--function-name hello-world \
--payload '{"name": "LocalStack"}' \
response.json

cat response.json

Questions​

  1. What is the content of response.json?
  2. Modify the code to add an invocation counter. Where would you store this counter?
  3. What happens if you pass an invalid payload (not JSON)?
💡 Solutions
  1. Content of response.json:
{
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": "{\"message\": \"Hello, LocalStack!\", \"timestamp\": \"...\", \"function\": \"hello-world\"}"
}
  1. Invocation counter:

    • DynamoDB for persistence
    • Global variable (reset on each cold start)
    • CloudWatch Metrics
  2. Invalid payload:

    • JSON parsing error
    • The function receives the raw string
    • Handle with try/except

Part 2: Function with Environment Variables​

Exercise 2.1: Configuration​

# config_demo.py
import json
import os

def lambda_handler(event, context):
"""
Demonstration of environment variables
"""
# Read environment variables
config = {
'environment': os.environ.get('ENVIRONMENT', 'development'),
'log_level': os.environ.get('LOG_LEVEL', 'INFO'),
'api_endpoint': os.environ.get('API_ENDPOINT', 'http://localhost'),
'max_retries': int(os.environ.get('MAX_RETRIES', '3'))
}

print(f"Configuration loaded: {json.dumps(config)}")

# Simulate different behavior based on the environment
if config['environment'] == 'production':
message = "Running in PRODUCTION mode"
else:
message = f"Running in {config['environment']} mode"

return {
'statusCode': 200,
'body': json.dumps({
'message': message,
'config': config
})
}

Exercise 2.2: Deployment with Variables​

# Create with environment variables
aws --endpoint-url=http://localhost:4566 lambda create-function \
--function-name config-demo \
--runtime python3.9 \
--role arn:aws:iam::000000000000:role/lambda-role \
--handler config_demo.lambda_handler \
--zip-file fileb://config_demo.zip \
--environment 'Variables={
ENVIRONMENT=staging,
LOG_LEVEL=DEBUG,
API_ENDPOINT=https://api.example.com,
MAX_RETRIES=5
}'

# Test
aws --endpoint-url=http://localhost:4566 lambda invoke \
--function-name config-demo \
response.json

# Modify a variable
aws --endpoint-url=http://localhost:4566 lambda update-function-configuration \
--function-name config-demo \
--environment 'Variables={
ENVIRONMENT=production,
LOG_LEVEL=WARN,
API_ENDPOINT=https://api.example.com,
MAX_RETRIES=3
}'

Questions​

Complete the table:

VariableDev ValueStaging ValueProd Value
ENVIRONMENT???
LOG_LEVEL???
MAX_RETRIES???
💡 Solution
VariableDev ValueStaging ValueProd Value
ENVIRONMENTdevelopmentstagingproduction
LOG_LEVELDEBUGDEBUGWARN
MAX_RETRIES353

Part 3: S3 Trigger (Simulated)​

Exercise 3.1: File Processor​

# s3_processor.py
import json

def lambda_handler(event, context):
"""
Process S3 events (file uploads)
"""
results = []

for record in event.get('Records', []):
# Extract S3 information
s3_info = record.get('s3', {})
bucket = s3_info.get('bucket', {}).get('name', 'unknown')
key = s3_info.get('object', {}).get('key', 'unknown')
size = s3_info.get('object', {}).get('size', 0)
event_type = record.get('eventName', 'unknown')

print(f"Processing: s3://{bucket}/{key}")
print(f"Event: {event_type}, Size: {size} bytes")

# Simulate processing
result = {
'bucket': bucket,
'key': key,
'size': size,
'event': event_type,
'status': 'processed'
}

# Conditional logic based on file type
if key.endswith('.jpg') or key.endswith('.png'):
result['action'] = 'image_processing'
elif key.endswith('.json'):
result['action'] = 'data_import'
elif key.endswith('.csv'):
result['action'] = 'csv_processing'
else:
result['action'] = 'archive'

results.append(result)
print(f"Result: {json.dumps(result)}")

return {
'statusCode': 200,
'body': json.dumps({
'processed': len(results),
'results': results
})
}

Exercise 3.2: Simulate an S3 Event​

# Create a simulated S3 event file
cat > s3_event.json << 'EOF'
{
"Records": [
{
"eventName": "ObjectCreated:Put",
"s3": {
"bucket": {"name": "my-uploads"},
"object": {
"key": "images/photo.jpg",
"size": 1024000
}
}
},
{
"eventName": "ObjectCreated:Put",
"s3": {
"bucket": {"name": "my-uploads"},
"object": {
"key": "data/report.csv",
"size": 5000
}
}
}
]
}
EOF

# Deploy and test
zip s3_processor.zip s3_processor.py

aws --endpoint-url=http://localhost:4566 lambda create-function \
--function-name s3-processor \
--runtime python3.9 \
--role arn:aws:iam::000000000000:role/lambda-role \
--handler s3_processor.lambda_handler \
--zip-file fileb://s3_processor.zip

aws --endpoint-url=http://localhost:4566 lambda invoke \
--function-name s3-processor \
--payload file://s3_event.json \
response.json

cat response.json | python -m json.tool

Questions​

  1. What action is assigned to photo.jpg?
  2. What action is assigned to report.csv?
  3. How would you modify the code to handle files > 1 MB differently?
💡 Solutions
  1. photo.jpg → action: image_processing
  2. report.csv → action: csv_processing
  3. Add a condition:
if size > 1_000_000:  # > 1 MB
result['processing_mode'] = 'async' # Asynchronous processing
else:
result['processing_mode'] = 'sync'

Part 4: Error Handling​

Exercise 4.1: Function with Errors​

# error_handling.py
import json

class ValidationError(Exception):
"""Custom validation error"""
pass

def validate_input(event):
"""Validate input data"""
if 'name' not in event:
raise ValidationError("The 'name' field is required")
if len(event['name']) < 2:
raise ValidationError("The name must be at least 2 characters long")
if 'age' in event and not isinstance(event['age'], int):
raise ValidationError("Age must be an integer")
return True

def lambda_handler(event, context):
"""
Function with comprehensive error handling
"""
print(f"Input: {json.dumps(event)}")

try:
# Validate input
validate_input(event)

# Processing
name = event['name']
age = event.get('age', 'not specified')

return {
'statusCode': 200,
'body': json.dumps({
'message': f'Hello {name}!',
'age': age,
'status': 'success'
})
}

except ValidationError as e:
print(f"Validation error: {str(e)}")
return {
'statusCode': 400,
'body': json.dumps({
'error': 'Validation Error',
'message': str(e),
'status': 'failed'
})
}

except Exception as e:
print(f"Unexpected error: {str(e)}")
return {
'statusCode': 500,
'body': json.dumps({
'error': 'Internal Server Error',
'message': 'An unexpected error occurred',
'status': 'failed'
})
}

Exercise 4.2: Scenario Tests​

Test with different payloads and complete the table:

PayloadStatus CodeError Type
{"name": "Alice"}??
{}??
{"name": "A"}??
{"name": "Bob", "age": "twenty"}??
{"name": "Charlie", "age": 25}??
💡 Solutions
PayloadStatus CodeError Type
{"name": "Alice"}200None
{}400ValidationError (name required)
{"name": "A"}400ValidationError (min 2 chars)
{"name": "Bob", "age": "twenty"}400ValidationError (age int)
{"name": "Charlie", "age": 25}200None

Part 5: Performance Analysis​

Exercise 5.1: Measure Execution Times​

# performance_test.py
import json
import time

def lambda_handler(event, context):
"""
Function to measure performance
"""
start_time = time.time()

# Simulate different workloads
workload = event.get('workload', 'light')

if workload == 'light':
# Light work
result = sum(range(1000))

elif workload == 'medium':
# Medium work
result = sum(range(100000))

elif workload == 'heavy':
# Heavy work
result = sum(range(10000000))

else:
result = 0

execution_time = (time.time() - start_time) * 1000 # in ms
remaining_time = context.get_remaining_time_in_millis()

return {
'statusCode': 200,
'body': json.dumps({
'workload': workload,
'result': result,
'execution_time_ms': round(execution_time, 2),
'remaining_time_ms': remaining_time,
'memory_limit_mb': context.memory_limit_in_mb
})
}

Analysis Questions​

  1. Run with workload: light, medium, and heavy. Note the times.
  2. What happens if the execution time exceeds the timeout?
  3. How would you optimize a slow function?

Final Deliverable​

Document to Produce​

  1. Code of the 4 functions (hello, config, s3, errors)
  2. Test results (payloads and responses)
  3. Error analysis table
  4. Optimization recommendations

Evaluation Criteria​

CriteriaPoints
Functions created and tested/30
Error handling/20
Environment variables/15
S3 event analysis/15
Code quality/10
Documentation/10
Total/100

Next week: MODULE 12 - Lambda Troubleshooting