Skip to content

Commit 424ed4b

Browse files
authored
CSHARP-4350: Add AWS Lambda tests (#1276)
1 parent 7c9b594 commit 424ed4b

File tree

10 files changed

+390
-0
lines changed

10 files changed

+390
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ src/packages
6767
benchmarks/MongoDB.Driver.Benchmarks/BenchmarkDotNet.Artifacts
6868
benchmarks/MongoDB.Driver.Benchmarks/data
6969

70+
# AWS Lambda Function Tests Artifacts
71+
tests/FaasTests/LambdaTests/.aws-sam
72+
7073
# Other
7174
artifacts
7275
packages

CSharpDriver.sln

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MongoDB.TestHelpers", "test
6868
EndProject
6969
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MongoDB.Driver.Benchmarks", "benchmarks\MongoDB.Driver.Benchmarks\MongoDB.Driver.Benchmarks.csproj", "{CF670F4A-49DD-4030-A4A0-1F4D600EB70A}"
7070
EndProject
71+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FaasTests", "FaasTests", "{66E1914C-9E10-4B28-915E-AB3773EFDAB2}"
72+
EndProject
73+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MongoDB.Driver.LambdaTest", "tests\FaasTests\LambdaTests\MongoDB.Driver.LambdaTest\MongoDB.Driver.LambdaTest.csproj", "{33B11279-DA4A-46EA-99BF-9DEDCAC50D95}"
74+
EndProject
7175
Global
7276
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7377
Debug|Any CPU = Debug|Any CPU
@@ -158,6 +162,10 @@ Global
158162
{CF670F4A-49DD-4030-A4A0-1F4D600EB70A}.Debug|Any CPU.Build.0 = Debug|Any CPU
159163
{CF670F4A-49DD-4030-A4A0-1F4D600EB70A}.Release|Any CPU.ActiveCfg = Release|Any CPU
160164
{CF670F4A-49DD-4030-A4A0-1F4D600EB70A}.Release|Any CPU.Build.0 = Release|Any CPU
165+
{33B11279-DA4A-46EA-99BF-9DEDCAC50D95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
166+
{33B11279-DA4A-46EA-99BF-9DEDCAC50D95}.Debug|Any CPU.Build.0 = Debug|Any CPU
167+
{33B11279-DA4A-46EA-99BF-9DEDCAC50D95}.Release|Any CPU.ActiveCfg = Release|Any CPU
168+
{33B11279-DA4A-46EA-99BF-9DEDCAC50D95}.Release|Any CPU.Build.0 = Release|Any CPU
161169
EndGlobalSection
162170
GlobalSection(SolutionProperties) = preSolution
163171
HideSolutionNode = FALSE
@@ -185,6 +193,8 @@ Global
185193
{F64BF86A-1EF1-4596-84A6-6B4AB766CD77} = {E472BDF5-61F1-461D-872B-9F53BB3ACA80}
186194
{DF888021-744F-4A8B-9324-831DEFC48AB8} = {E472BDF5-61F1-461D-872B-9F53BB3ACA80}
187195
{CF670F4A-49DD-4030-A4A0-1F4D600EB70A} = {503AB075-7C23-475B-BF7D-E7F751A31536}
196+
{66E1914C-9E10-4B28-915E-AB3773EFDAB2} = {E472BDF5-61F1-461D-872B-9F53BB3ACA80}
197+
{33B11279-DA4A-46EA-99BF-9DEDCAC50D95} = {66E1914C-9E10-4B28-915E-AB3773EFDAB2}
188198
EndGlobalSection
189199
GlobalSection(ExtensibilityGlobals) = postSolution
190200
SolutionGuid = {24BEC44B-92B0-43AA-9B15-163459D0C098}

evergreen/evergreen.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,23 @@ tasks:
14591459
- func: install-dotnet
14601460
- func: run-performance-tests
14611461

1462+
- name: test-aws-lambda-deployed
1463+
commands:
1464+
- command: ec2.assume_role
1465+
params:
1466+
role_arn: ${LAMBDA_AWS_ROLE_ARN}
1467+
duration_seconds: 3600
1468+
- command: shell.exec
1469+
params:
1470+
working_dir: mongo-csharp-driver
1471+
add_expansions_to_env: true
1472+
script: |
1473+
${PREPARE_SHELL}
1474+
evergreen/run-deployed-lambda-aws-tests.sh
1475+
env:
1476+
TEST_LAMBDA_DIRECTORY: ${PROJECT_DIRECTORY}/tests/FaasTests/LambdaTests
1477+
AWS_REGION: us-east-1
1478+
14621479
# ECDSA tests
14631480
# Disabled until https://jira.mongodb.org/browse/SPEC-1589 is resolved
14641481
# - name: test-ocsp-ecdsa-valid-cert-server-staples-ca-responder
@@ -2058,6 +2075,32 @@ task_groups:
20582075
tasks:
20592076
- atlas-search-index-helpers-test
20602077

2078+
- name: test-aws-lambda-task-group
2079+
setup_group_can_fail_task: true
2080+
setup_group_timeout_secs: 1800 # 30 minutes
2081+
setup_group:
2082+
- func: fetch-source
2083+
- func: prepare-resources
2084+
- func: install-dotnet
2085+
- command: subprocess.exec
2086+
params:
2087+
binary: bash
2088+
add_expansions_to_env: true
2089+
args:
2090+
- ${DRIVERS_TOOLS}/.evergreen/atlas/setup-atlas-cluster.sh
2091+
- command: expansions.update
2092+
params:
2093+
file: atlas-expansion.yml
2094+
teardown_group:
2095+
- command: subprocess.exec
2096+
params:
2097+
binary: bash
2098+
add_expansions_to_env: true
2099+
args:
2100+
- ${DRIVERS_TOOLS}/.evergreen/atlas/teardown-atlas-cluster.sh
2101+
tasks:
2102+
- test-aws-lambda-deployed
2103+
20612104
buildvariants:
20622105
- matrix_name: stable-api-tests
20632106
matrix_spec: { version: ["5.0", "6.0", "7.0", "rapid", "latest"], topology: "standalone", auth: "auth", ssl: "nossl", os: "windows-64" }
@@ -2257,6 +2300,14 @@ buildvariants:
22572300
tasks:
22582301
- name: performance-tests-net60-server-v6.0
22592302

2303+
# AWS Lambda tests
2304+
- name: aws-lambda-tests
2305+
display_name: "AWS Lambda Tests"
2306+
run_on:
2307+
- ubuntu2204-small
2308+
tasks:
2309+
- name: test-aws-lambda-task-group
2310+
22602311
# Atlas tests
22612312
- name: atlas-connectivity-tests
22622313
display_name: "Atlas Connectivity Tests"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env bash
2+
3+
set -o xtrace # Write all commands first to stderr
4+
set -o errexit # Exit the script with error if any of the commands fail
5+
6+
LOCALTOOLPATH=$TEST_LAMBDA_DIRECTORY/localtool
7+
8+
echo "Installing aws lambda tool to a custom location"
9+
dotnet tool install "Amazon.Lambda.Tools" --tool-path $LOCALTOOLPATH
10+
11+
# makes the "sam build" command use the local version of the lambda tool
12+
export PATH="$PATH:$LOCALTOOLPATH"
13+
14+
. ${DRIVERS_TOOLS}/.evergreen/aws_lambda/run-deployed-lambda-aws-tests.sh
15+
16+
# uninstall global version installed by the "sam build" command
17+
dotnet tool uninstall -g "Amazon.Lambda.Tools"
18+
19+
# uninstall our local version
20+
dotnet tool uninstall "Amazon.Lambda.Tools" --tool-path $LOCALTOOLPATH
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Text.Json;
19+
using System.Threading;
20+
using System.Threading.Tasks;
21+
using Amazon.Lambda.APIGatewayEvents;
22+
using Amazon.Lambda.Core;
23+
using Amazon.Lambda.Serialization.SystemTextJson;
24+
using MongoDB.Bson;
25+
using MongoDB.Driver.Core.Events;
26+
27+
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
28+
[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
29+
30+
namespace MongoDB.Driver.LambdaTest
31+
{
32+
public class LambdaFunction
33+
{
34+
private MongoClient _mongoClient;
35+
private long _openConnections;
36+
private long _totalHeartbeatCount;
37+
private double _totalHeartbeatDurationMs;
38+
private long _totalCommandCount;
39+
private double _totalCommandDurationMs;
40+
private bool _serverHeartbeatWasAwaited;
41+
42+
public LambdaFunction()
43+
{
44+
_openConnections = 0;
45+
_totalHeartbeatCount = 0;
46+
_totalHeartbeatDurationMs = 0;
47+
_totalCommandCount = 0;
48+
_totalCommandDurationMs = 0;
49+
_serverHeartbeatWasAwaited = false;
50+
51+
var connectionString = Environment.GetEnvironmentVariable("MONGODB_URI");
52+
53+
// register listeners for the to be monitored events
54+
var clientSettings = MongoClientSettings.FromConnectionString(connectionString);
55+
clientSettings.ClusterConfigurator = builder =>
56+
{
57+
builder
58+
.Subscribe<ServerHeartbeatSucceededEvent>(Handle)
59+
.Subscribe<ServerHeartbeatFailedEvent>(Handle)
60+
.Subscribe<CommandSucceededEvent>(Handle)
61+
.Subscribe<CommandFailedEvent>(Handle)
62+
.Subscribe<ConnectionCreatedEvent>(Handle)
63+
.Subscribe<ConnectionClosedEvent>(Handle);
64+
};
65+
66+
// Client is used for all requests
67+
_mongoClient = new MongoClient(clientSettings);
68+
}
69+
70+
public async Task<APIGatewayProxyResponse> LambdaFunctionHandlerAsync(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
71+
{
72+
var collection = _mongoClient.GetDatabase("lambdaTest").GetCollection<BsonDocument>("test");
73+
var testDocument = new BsonDocument("n", 1);
74+
await collection.InsertOneAsync(testDocument);
75+
await collection.DeleteOneAsync(doc => doc["_id"] == testDocument["_id"]);
76+
77+
Dictionary<string, string> responseBody;
78+
if (_serverHeartbeatWasAwaited)
79+
{
80+
// This is an error since the streaming protocol should be disabled in a FaaS environment thus no server heartbeat should be awaited
81+
responseBody = new Dictionary<string, string>
82+
{
83+
{ "message", "ERROR: no ServerHeartBeat events should contain the awaited=true flag." },
84+
};
85+
}
86+
else
87+
{
88+
responseBody = new Dictionary<string, string>
89+
{
90+
{ "averageCommandDurationMS", $"{_totalCommandDurationMs / _totalCommandCount :F3}"},
91+
{ "averageServerHeartbeatDurationMS", $"{_totalHeartbeatDurationMs / _totalHeartbeatCount :F3}"},
92+
{ "openConnections", $"{_openConnections}" },
93+
{ "heartBeatCount", $"{_totalHeartbeatCount}" },
94+
{ "commandCount", $"{_totalCommandCount}" },
95+
};
96+
}
97+
98+
Reset();
99+
100+
return new APIGatewayProxyResponse
101+
{
102+
Body = JsonSerializer.Serialize(responseBody, new JsonSerializerOptions { WriteIndented = true}),
103+
StatusCode = _serverHeartbeatWasAwaited ? 502 : 200,
104+
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
105+
};
106+
}
107+
108+
// actions for monitored events
109+
private void Handle(ServerHeartbeatSucceededEvent @event)
110+
{
111+
_totalHeartbeatCount++;
112+
_totalHeartbeatDurationMs += @event.Duration.TotalMilliseconds;
113+
_serverHeartbeatWasAwaited |= @event.Awaited;
114+
}
115+
116+
private void Handle(ServerHeartbeatFailedEvent @event)
117+
{
118+
_totalHeartbeatCount++;
119+
_totalHeartbeatDurationMs += @event.Duration.TotalMilliseconds;
120+
_serverHeartbeatWasAwaited |= @event.Awaited;
121+
}
122+
123+
private void Handle(CommandSucceededEvent @event)
124+
{
125+
_totalCommandCount++;
126+
_totalCommandDurationMs += @event.Duration.TotalMilliseconds;
127+
}
128+
129+
private void Handle(CommandFailedEvent @event)
130+
{
131+
_totalCommandCount++;
132+
_totalCommandDurationMs += @event.Duration.TotalMilliseconds;
133+
}
134+
135+
private void Handle(ConnectionCreatedEvent @event)
136+
{
137+
Interlocked.Increment(ref _openConnections);
138+
}
139+
140+
private void Handle(ConnectionClosedEvent @event)
141+
{
142+
Interlocked.Decrement(ref _openConnections);
143+
}
144+
145+
private void Reset()
146+
{
147+
_totalHeartbeatCount = 0;
148+
_totalHeartbeatDurationMs = 0;
149+
_totalCommandCount = 0;
150+
_totalCommandDurationMs = 0;
151+
}
152+
}
153+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
6+
<AWSProjectType>Lambda</AWSProjectType>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" />
11+
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.0" />
12+
<PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.7.0" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\..\..\src\MongoDB.Driver\MongoDB.Driver.csproj" />
17+
</ItemGroup>
18+
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"Information": [
3+
"This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
4+
"To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
5+
"dotnet lambda help",
6+
"All the command line options for the Lambda command can be specified in this file."
7+
],
8+
"profile": "",
9+
"region": "",
10+
"configuration": "Release",
11+
"template": "template.yaml"
12+
}

tests/FaasTests/LambdaTests/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# AWS Lambda Testing
2+
3+
This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders.
4+
5+
- MongoDB.Driver.LambdaTest - Code for the application's Lambda function.
6+
- template.yaml - A template that defines the application's AWS resources.
7+
8+
The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates the application code.
9+
10+
## Running Locally
11+
12+
Prerequisites:
13+
14+
- AWS SAM CLI
15+
- Docker daemon running with mongodb instance
16+
17+
Build the application with the `sam build` command from the `tests/FaasTests/LambdaTests` folder.
18+
19+
```bash
20+
sam build
21+
```
22+
23+
The SAM CLI installs dependencies defined in `./MongoDB.Driver.LambdaTest/MongoDB.Driver.LambdaTest.csproj`, creates a deployment package, and saves it in a `.aws-sam/build` folder.
24+
25+
Run the function locally and invoke them with the `sam local invoke` command.
26+
27+
```bash
28+
sam local invoke --parameter-overrides "MongoDbUri=mongodb://host.docker.internal:27017"
29+
```
30+
31+
## Resources
32+
33+
See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# More information about the configuration file can be found here:
2+
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
3+
version = 0.1
4+
5+
[default]
6+
[default.global.parameters]
7+
stack_name = "LambdaTests"
8+
9+
[default.build.parameters]
10+
cached = true
11+
parallel = true
12+
13+
[default.validate.parameters]
14+
lint = true
15+
16+
[default.deploy.parameters]
17+
capabilities = "CAPABILITY_IAM"
18+
confirm_changeset = true
19+
resolve_s3 = true
20+
21+
[default.package.parameters]
22+
resolve_s3 = true
23+
24+
[default.sync.parameters]
25+
watch = true
26+
27+
[default.local_start_api.parameters]
28+
warm_containers = "EAGER"
29+
30+
[default.local_start_lambda.parameters]
31+
warm_containers = "EAGER"

0 commit comments

Comments
 (0)