Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
/target
/.cargo
lambda-runtime/libtest.rmeta
lambda-integration-tests/target
Cargo.lock

# Built AWS Lambda zipfile
lambda.zip

# output.json from example docs
output.json

.aws-sam
build
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"lambda-http",
"lambda-integration-tests",
"lambda-runtime-api-client",
"lambda-runtime",
"lambda-extension"
Expand Down
36 changes: 36 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
INTEG_STACK_NAME ?= rust-lambda-integration-tests
INTEG_FUNCTIONS_BUILD := runtime-fn runtime-trait
INTEG_FUNCTIONS_INVOKE := RuntimeFn RuntimeFnAl2 RuntimeTrait RuntimeTraitAl2 Python PythonAl2
INTEG_EXTENSIONS := extension-fn extension-trait
# Using musl to run extensions on both AL1 and AL2
INTEG_ARCH := x86_64-unknown-linux-musl

integration-tests:
# Build Integration functions
cross build --release --target $(INTEG_ARCH) -p lambda_integration_tests
rm -rf ./build
mkdir -p ./build
${MAKE} ${MAKEOPTS} $(foreach function,${INTEG_FUNCTIONS_BUILD}, build-integration-function-${function})
${MAKE} ${MAKEOPTS} $(foreach extension,${INTEG_EXTENSIONS}, build-integration-extension-${extension})
# Deploy to AWS
sam deploy \
--template lambda-integration-tests/template.yaml \
--stack-name ${INTEG_STACK_NAME} \
--capabilities CAPABILITY_IAM \
--resolve-s3 \
--no-fail-on-empty-changeset
# Invoke functions
${MAKE} ${MAKEOPTS} $(foreach function,${INTEG_FUNCTIONS_INVOKE}, invoke-integration-function-${function})

build-integration-function-%:
mkdir -p ./build/$*
cp -v ./target/$(INTEG_ARCH)/release/$* ./build/$*/bootstrap

build-integration-extension-%:
mkdir -p ./build/$*/extensions
cp -v ./target/$(INTEG_ARCH)/release/$* ./build/$*/extensions/$*

invoke-integration-function-%:
aws lambda invoke --function-name $$(aws cloudformation describe-stacks --stack-name $(INTEG_STACK_NAME) \
--query 'Stacks[0].Outputs[?OutputKey==`$*`].OutputValue' \
--output text) --payload '{"command": "hello"}' --cli-binary-format raw-in-base64-out /dev/stdout
3 changes: 1 addition & 2 deletions lambda-extension/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
use hyper::client::{connect::Connection, HttpConnector};
use lambda_runtime_api_client::Client;
use serde::Deserialize;
use std::future::Future;
use std::path::PathBuf;
use std::{future::Future, path::PathBuf};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_stream::StreamExt;
use tower_service::Service;
Expand Down
21 changes: 21 additions & 0 deletions lambda-integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "lambda_integration_tests"
version = "0.4.1"
edition = "2018"
description = "AWS Lambda Runtime integration tests"
license = "Apache-2.0"
repository = "https://github.com/awslabs/aws-lambda-rust-runtime"
categories = ["web-programming::http-server"]
keywords = ["AWS", "Lambda", "API"]
readme = "../README.md"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
lambda_http = { path = "../lambda-http", version = "0.4.1" }
lambda_runtime = { path = "../lambda-runtime", version = "0.4.1" }
lambda_extension = { path = "../lambda-extension", version = "0.1.0" }
log = "0.4"
serde = { version = "1", features = ["derive"] }
simple_logger = { version = "1.15", default-features = false }
tokio = { version = "1", features = ["full"] }
4 changes: 4 additions & 0 deletions lambda-integration-tests/python/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def handler(event, context):
return {
"message": event["command"].upper()
}
23 changes: 23 additions & 0 deletions lambda-integration-tests/src/bin/extension-fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use lambda_extension::{extension_fn, Error, NextEvent};
use log::{info, LevelFilter};
use simple_logger::SimpleLogger;

async fn my_extension(event: NextEvent) -> Result<(), Error> {
match event {
NextEvent::Shutdown(e) => {
info!("[extension-fn] Shutdown event received: {:?}", e);
}
NextEvent::Invoke(e) => {
info!("[extension-fn] Request event received: {:?}", e);
}
}

Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap();

lambda_extension::run(extension_fn(my_extension)).await
}
42 changes: 42 additions & 0 deletions lambda-integration-tests/src/bin/extension-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use lambda_extension::{Error, Extension, NextEvent};
use log::{info, LevelFilter};
use simple_logger::SimpleLogger;
use std::{
future::{ready, Future},
pin::Pin,
};

struct MyExtension {
invoke_count: usize,
}

impl Default for MyExtension {
fn default() -> Self {
Self { invoke_count: 0 }
}
}

impl Extension for MyExtension {
type Fut = Pin<Box<dyn Future<Output = Result<(), Error>>>>;

fn call(&mut self, event: NextEvent) -> Self::Fut {
match event {
NextEvent::Shutdown(e) => {
info!("[extension] Shutdown event received: {:?}", e);
}
NextEvent::Invoke(e) => {
self.invoke_count += 1;
info!("[extension] Request event {} received: {:?}", self.invoke_count, e);
}
}

Box::pin(ready(Ok(())))
}
}

#[tokio::main]
async fn main() -> Result<(), Error> {
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap();

lambda_extension::run(MyExtension::default()).await
}
29 changes: 29 additions & 0 deletions lambda-integration-tests/src/bin/runtime-fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use lambda_runtime::{handler_fn, Context, Error};
use log::{info, LevelFilter};
use serde::{Deserialize, Serialize};
use simple_logger::SimpleLogger;

#[derive(Deserialize, Debug)]
struct Request {
command: String,
}

#[derive(Serialize, Debug)]
struct Response {
message: String,
}

async fn handler(event: Request, _context: Context) -> Result<Response, Error> {
info!("[handler-fn] Received event: {:?}", event);

Ok(Response {
message: event.command.to_uppercase(),
})
}

#[tokio::main]
async fn main() -> Result<(), Error> {
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap();

lambda_runtime::run(handler_fn(handler)).await
}
48 changes: 48 additions & 0 deletions lambda-integration-tests/src/bin/runtime-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use lambda_runtime::{Context, Error, Handler};
use log::{info, LevelFilter};
use serde::{Deserialize, Serialize};
use simple_logger::SimpleLogger;
use std::{
future::{ready, Future},
pin::Pin,
};

#[derive(Deserialize, Debug)]
struct Request {
command: String,
}

#[derive(Serialize, Debug)]
struct Response {
message: String,
}

struct MyHandler {
invoke_count: usize,
}

impl Default for MyHandler {
fn default() -> Self {
Self { invoke_count: 0 }
}
}

impl Handler<Request, Response> for MyHandler {
type Error = Error;
type Fut = Pin<Box<dyn Future<Output = Result<Response, Error>>>>;

fn call(&mut self, event: Request, _context: Context) -> Self::Fut {
self.invoke_count += 1;
info!("[handler] Received event {}: {:?}", self.invoke_count, event);
Box::pin(ready(Ok(Response {
message: event.command.to_uppercase(),
})))
}
}

#[tokio::main]
async fn main() -> Result<(), Error> {
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap();

lambda_runtime::run(MyHandler::default()).await
}
95 changes: 95 additions & 0 deletions lambda-integration-tests/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
Function:
MemorySize: 128
Handler: bootstrap
Timeout: 5

Resources:
# Rust function using runtime_fn running on AL2
RuntimeFnAl2:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../build/runtime-fn/
Runtime: provided.al2
Layers:
- !Ref ExtensionFn
- !Ref ExtensionTrait

# Rust function using runtime_fn running on AL1
RuntimeFn:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../build/runtime-fn/
Runtime: provided
Layers:
- !Ref ExtensionFn
- !Ref ExtensionTrait

# Rust function using a Handler implementation running on AL2
RuntimeTraitAl2:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../build/runtime-trait/
Runtime: provided.al2
Layers:
- !Ref ExtensionFn
- !Ref ExtensionTrait

# Rust function using a Handler implementation running on AL1
RuntimeTrait:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../build/runtime-trait/
Runtime: provided
Layers:
- !Ref ExtensionFn
- !Ref ExtensionTrait

# Python function running on AL2
PythonAl2:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./python/
Handler: main.handler
Runtime: python3.9
Layers:
- !Ref ExtensionFn
- !Ref ExtensionTrait

# Python function running on AL1
Python:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./python/
Handler: main.handler
Runtime: python3.7
Layers:
- !Ref ExtensionFn
- !Ref ExtensionTrait

ExtensionFn:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: ../build/extension-fn/

ExtensionTrait:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: ../build/extension-trait/

Outputs:
RuntimeFnAl2:
Value: !GetAtt RuntimeFnAl2.Arn
RuntimeFn:
Value: !GetAtt RuntimeFn.Arn
RuntimeTraitAl2:
Value: !GetAtt RuntimeTraitAl2.Arn
RuntimeTrait:
Value: !GetAtt RuntimeTrait.Arn
PythonAl2:
Value: !GetAtt PythonAl2.Arn
Python:
Value: !GetAtt Python.Arn