Skip to content

Add FaaS (AWS Lambda) test app #1183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
49 changes: 49 additions & 0 deletions .evergreen/.evg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,23 @@ tasks:
- func: "run perf tests"
- func: "send dashboard data"

- name: "test-aws-lambda-deployed"
commands:
- command: ec2.assume_role
params:
role_arn: ${LAMBDA_AWS_ROLE_ARN}
duration_seconds: 3600
- command: subprocess.exec
params:
working_dir: src
binary: bash
add_expansions_to_env: true
args:
- .evergreen/run-deployed-lambda-aws-tests.sh
env:
TEST_LAMBDA_DIRECTORY: ${PROJECT_DIRECTORY}/driver-lambda/
AWS_REGION: us-east-1

- name: "mmapv1-storage-test"
commands:
- func: "bootstrap mongo-orchestration"
Expand Down Expand Up @@ -1891,6 +1908,32 @@ task_groups:
$DRIVERS_TOOLS/.evergreen/csfle/azurekms/delete-vm.sh
tasks:
- testazurekms-task
- name: test_atlas_task_group
setup_group:
- func: fetch source
- func: prepare resources
- command: subprocess.exec
params:
working_dir: src
binary: bash
add_expansions_to_env: true
args:
- ${DRIVERS_TOOLS}/.evergreen/atlas/setup-atlas-cluster.sh
- command: expansions.update
params:
file: src/atlas-expansion.yml
teardown_group:
- command: subprocess.exec
params:
working_dir: src
binary: bash
add_expansions_to_env: true
args:
- ${DRIVERS_TOOLS}/.evergreen/atlas/teardown-atlas-cluster.sh
setup_group_can_fail_task: true
setup_group_timeout_secs: 1800
tasks:
- test-aws-lambda-deployed

buildvariants:

Expand Down Expand Up @@ -2023,6 +2066,12 @@ buildvariants:
tasks:
- name: "perf"

- name: rhel8-test-atlas
display_name: Atlas Cluster Tests
run_on: rhel80-large
tasks:
- test_atlas_task_group
Comment on lines +2069 to +2073
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rozza Should this run when a commit is merged into master, and if so, where could I set that up? (I did not see anything definite in the project settings.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question!

Evergreen monitors a set of github repositories, waiting for new commits. When a new commit comes in or enough time has passed, Evergreen schedules builds for different variants (different OSes, compile flags, etc). [1]

So as the evergreen project monitors the master branch, all build variants will run by default.

See limiting when a task will run to learn about opting out. One such example is the publish release task which opts out by setting git_tag_only: true.

Our evg.yml dates back from early evergreen days - I find it hard to grok and I wonder if there are newer features that we don't use. (a thought for another day).


- name: plain-auth-test
display_name: "PLAIN (LDAP) Auth test"
run_on: rhel80-small
Expand Down
13 changes: 13 additions & 0 deletions .evergreen/run-deployed-lambda-aws-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -o xtrace # Write all commands first to stderr
set -o errexit # Exit the script with error if any of the commands fail

RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE[0]:-$0}")"
. "${RELATIVE_DIR_PATH}/javaConfig.bash"

# compiled outside of lambda workflow. Note "SkipBuild: True" in template.yaml
./gradlew -version
./gradlew --info driver-lambda:shadowJar

. ${DRIVERS_TOOLS}/.evergreen/aws_lambda/run-deployed-lambda-aws-tests.sh
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def coreProjects = subprojects - utilProjects
def scalaProjects = subprojects.findAll { it.name.contains('scala') }
def javaProjects = subprojects - scalaProjects
def javaMainProjects = javaProjects - utilProjects
def javaCodeCheckedProjects = javaMainProjects.findAll { !['driver-benchmarks', 'driver-workload-executor'].contains(it.name) }
def javaCodeCheckedProjects = javaMainProjects.findAll { !['driver-benchmarks', 'driver-workload-executor', 'driver-lambda'].contains(it.name) }
def javaAndScalaTestedProjects = javaCodeCheckedProjects + scalaProjects

configure(coreProjects) {
Expand Down
74 changes: 74 additions & 0 deletions driver-lambda/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:6.1.0'
}
}

plugins {
id("application")
}

apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'java'

compileJava {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

mainClassName = "com.mongodb.workload.WorkloadExecutor"

sourceSets {
main {
java {
srcDir 'src/main'
}
resources {
srcDir 'src/resources'
}
}
}

dependencies {
implementation project(':driver-sync')
implementation project(':bson')

implementation('com.amazonaws:aws-lambda-java-core:1.2.2')
implementation('com.amazonaws:aws-lambda-java-events:3.11.1')
}


javadoc {
enabled = false
}

jar {
manifest {
attributes "Main-Class": "com.mongodb.lambdatest.LambdaTestApp"
}
}

shadowJar {
archiveBaseName.set('lambdatest')
archiveVersion.set('')
}
31 changes: 31 additions & 0 deletions driver-lambda/samconfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1

[default]
[default.global.parameters]
stack_name = "lambdatest"

[default.build.parameters]
cached = true
parallel = false

[default.validate.parameters]
lint = true

[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = false # headless
resolve_s3 = true

[default.package.parameters]
resolve_s3 = true

[default.sync.parameters]
watch = true

[default.local_start_api.parameters]
warm_containers = "EAGER"

[default.local_start_lambda.parameters]
warm_containers = "EAGER"
154 changes: 154 additions & 0 deletions driver-lambda/src/main/com/mongodb/lambdatest/LambdaTestApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.lambdatest;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandListener;
import com.mongodb.event.CommandSucceededEvent;
import com.mongodb.event.ConnectionClosedEvent;
import com.mongodb.event.ConnectionCreatedEvent;
import com.mongodb.event.ConnectionPoolListener;
import com.mongodb.event.ServerHeartbeatFailedEvent;
import com.mongodb.event.ServerHeartbeatSucceededEvent;
import com.mongodb.event.ServerMonitorListener;
import com.mongodb.lang.NonNull;
import org.bson.BsonDocument;
import org.bson.BsonInt64;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.Document;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

/**
* Test App for AWS lambda functions
*/
public class LambdaTestApp implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
private final MongoClient mongoClient;
private long openConnections = 0;
private long totalHeartbeatCount = 0;
private long totalHeartbeatDurationMs = 0;
private long totalCommandCount = 0;
private long totalCommandDurationMs = 0;

public LambdaTestApp() {
String connectionString = System.getenv("MONGODB_URI");

MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(connectionString))
.addCommandListener(new CommandListener() {
@Override
public void commandSucceeded(@NonNull final CommandSucceededEvent event) {
totalCommandCount++;
totalCommandDurationMs += event.getElapsedTime(MILLISECONDS);
}
@Override
public void commandFailed(@NonNull final CommandFailedEvent event) {
totalCommandCount++;
totalCommandDurationMs += event.getElapsedTime(MILLISECONDS);
}
})
.applyToServerSettings(builder -> builder.addServerMonitorListener(new ServerMonitorListener() {
@Override
public void serverHeartbeatSucceeded(@NonNull final ServerHeartbeatSucceededEvent event) {
totalHeartbeatCount++;
totalHeartbeatDurationMs += event.getElapsedTime(MILLISECONDS);
}
@Override
public void serverHeartbeatFailed(@NonNull final ServerHeartbeatFailedEvent event) {
totalHeartbeatCount++;
totalHeartbeatDurationMs += event.getElapsedTime(MILLISECONDS);
}
}))
.applyToConnectionPoolSettings(builder -> builder.addConnectionPoolListener(new ConnectionPoolListener() {
@Override
public void connectionCreated(@NonNull final ConnectionCreatedEvent event) {
openConnections++;
}
@Override
public void connectionClosed(@NonNull final ConnectionClosedEvent event) {
openConnections--;
}
}))
.build();
mongoClient = MongoClients.create(settings);
}

public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
try {
MongoCollection<Document> collection = mongoClient
.getDatabase("lambdaTest")
.getCollection("test");
BsonValue id = collection.insertOne(new Document("n", 1)).getInsertedId();
collection.deleteOne(new Document("_id", id));

BsonDocument responseBody = getBsonDocument();

return templateResponse()
.withStatusCode(200)
.withBody(responseBody.toJson());

} catch (Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
BsonDocument responseBody = new BsonDocument()
.append("throwable", new BsonString(e.getMessage()))
.append("stacktrace", new BsonString(sw.toString()));
return templateResponse()
.withBody(responseBody.toJson())
.withStatusCode(500);
}
}

private BsonDocument getBsonDocument() {
BsonDocument responseBody = new BsonDocument()
.append("totalCommandDurationMs", new BsonInt64(totalCommandDurationMs))
.append("totalCommandCount", new BsonInt64(totalCommandCount))
.append("totalHeartbeatDurationMs", new BsonInt64(totalHeartbeatDurationMs))
.append("totalHeartbeatCount", new BsonInt64(totalHeartbeatCount))
.append("openConnections", new BsonInt64(openConnections));

totalCommandDurationMs = 0;
totalCommandCount = 0;
totalHeartbeatCount = 0;
totalHeartbeatDurationMs = 0;

return responseBody;
}

private APIGatewayProxyResponseEvent templateResponse() {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("X-Custom-Header", "application/json");
return new APIGatewayProxyResponseEvent()
.withHeaders(headers);
}
}
Loading