From a60a68ae174d2103adcb75f4ecc5935974e1bf20 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 11 Oct 2023 12:51:56 -0600 Subject: [PATCH] let's see if we can get the FaaS testing stuff to work --- .evergreen/aws_lambda | 1 + .evergreen/config.yml | 52 ++++++++------ .evergreen/config/common.yml.erb | 52 ++++++++------ Rakefile | 26 +++++++ spec/support/faas/app/aws_lambda/Gemfile | 8 +++ spec/support/faas/app/aws_lambda/README.md | 18 +++++ .../faas/app/aws_lambda/bundle-gems.sh | 16 +++++ .../faas/app/aws_lambda/mongodb/Gemfile | 7 ++ .../faas/app/aws_lambda/mongodb/app.rb | 28 ++++++++ .../aws_lambda/mongodb/faas_test/runner.rb | 68 +++++++++++++++++++ .../mongodb/faas_test/subscribers/command.rb | 23 +++++++ .../faas_test/subscribers/connection_pool.rb | 22 ++++++ .../faas_test/subscribers/heartbeat.rb | 28 ++++++++ .../faas/app/aws_lambda/samconfig.toml | 31 +++++++++ .../support/faas/app/aws_lambda/template.yaml | 47 +++++++++++++ 15 files changed, 389 insertions(+), 38 deletions(-) create mode 120000 .evergreen/aws_lambda create mode 100644 spec/support/faas/app/aws_lambda/Gemfile create mode 100644 spec/support/faas/app/aws_lambda/README.md create mode 100755 spec/support/faas/app/aws_lambda/bundle-gems.sh create mode 100644 spec/support/faas/app/aws_lambda/mongodb/Gemfile create mode 100644 spec/support/faas/app/aws_lambda/mongodb/app.rb create mode 100644 spec/support/faas/app/aws_lambda/mongodb/faas_test/runner.rb create mode 100644 spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/command.rb create mode 100644 spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/connection_pool.rb create mode 100644 spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/heartbeat.rb create mode 100644 spec/support/faas/app/aws_lambda/samconfig.toml create mode 100644 spec/support/faas/app/aws_lambda/template.yaml diff --git a/.evergreen/aws_lambda b/.evergreen/aws_lambda new file mode 120000 index 0000000000..3366dcbced --- /dev/null +++ b/.evergreen/aws_lambda @@ -0,0 +1 @@ +../.mod/drivers-evergreen-tools/.evergreen/aws_lambda \ No newline at end of file diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 76631a375a..40bef114c4 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -128,6 +128,18 @@ functions: export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" + export LAMBDA_AWS_ROLE_ARN="${LAMBDA_AWS_ROLE_ARN}" + + # For starting/stopping/managing an atlas cluster + export DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" + export DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" + export DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" + export DRIVERS_ATLAS_LAMBDA_USER="${DRIVERS_ATLAS_LAMBDA_USER}" + export DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" + export LAMBDA_STACK_NAME="dbx-ruby-lambda" + export MONGODB_VERSION="7.0" + export task_id="${task_id}" + export execution="${execution}" # Needed for generating temporary aws credentials. if [ -n "${FLE}" ]; @@ -481,17 +493,7 @@ task_groups: working_dir: "src" script: | ${PREPARE_SHELL} - - DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ - DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ - DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ - DRIVERS_ATLAS_LAMBDA_USER="${DRIVERS_ATLAS_LAMBDA_USER}" \ - DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" \ - LAMBDA_STACK_NAME="dbx-ruby-lambda" \ - MONGODB_VERSION="7.0" \ - task_id="${task_id}" \ - execution="${execution}" \ - $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh + $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh - command: expansions.update params: file: src/atlas-expansion.yml @@ -502,16 +504,10 @@ task_groups: working_dir: "src" script: | ${PREPARE_SHELL} - - DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ - DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ - DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ - LAMBDA_STACK_NAME="dbx-ruby-lambda" \ - task_id="${task_id}" \ - execution="${execution}" \ - $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh + $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh tasks: - test-full-atlas-task + - test-faas-aws-lambda-task - name: testgcpkms_task_group setup_group_can_fail_task: true @@ -640,6 +636,24 @@ tasks: script: | ${PREPARE_SHELL} MONGODB_URI="${MONGODB_URI}" .evergreen/run-tests-atlas-full.sh + - name: "test-faas-aws-lambda-task" + commands: + - command: ec2.assume_role + params: + role_arn: ${LAMBDA_AWS_ROLE_ARN} + duration_seconds: 3600 + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} + + TEST_LAMBDA_DIRECTORY="${PROJECT_DIRECTORY}/spec/support/faas/app/aws_lambda" \ + AWS_REGION="us-east-1" \ + MONGODB_URI="${MONGODB_URI}" \ + .evergreen/aws_lambda/run-deployed-lambda-aws-tests.sh - name: "testgcpkms-task" commands: - command: shell.exec diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index e78f08d846..811bb126ca 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -125,6 +125,18 @@ functions: export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" + export LAMBDA_AWS_ROLE_ARN="${LAMBDA_AWS_ROLE_ARN}" + + # For starting/stopping/managing an atlas cluster + export DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" + export DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" + export DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" + export DRIVERS_ATLAS_LAMBDA_USER="${DRIVERS_ATLAS_LAMBDA_USER}" + export DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" + export LAMBDA_STACK_NAME="dbx-ruby-lambda" + export MONGODB_VERSION="7.0" + export task_id="${task_id}" + export execution="${execution}" # Needed for generating temporary aws credentials. if [ -n "${FLE}" ]; @@ -478,17 +490,7 @@ task_groups: working_dir: "src" script: | ${PREPARE_SHELL} - - DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ - DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ - DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ - DRIVERS_ATLAS_LAMBDA_USER="${DRIVERS_ATLAS_LAMBDA_USER}" \ - DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" \ - LAMBDA_STACK_NAME="dbx-ruby-lambda" \ - MONGODB_VERSION="7.0" \ - task_id="${task_id}" \ - execution="${execution}" \ - $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh + $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh - command: expansions.update params: file: src/atlas-expansion.yml @@ -499,16 +501,10 @@ task_groups: working_dir: "src" script: | ${PREPARE_SHELL} - - DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ - DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ - DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ - LAMBDA_STACK_NAME="dbx-ruby-lambda" \ - task_id="${task_id}" \ - execution="${execution}" \ - $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh + $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh tasks: - test-full-atlas-task + - test-faas-aws-lambda-task - name: testgcpkms_task_group setup_group_can_fail_task: true @@ -637,6 +633,24 @@ tasks: script: | ${PREPARE_SHELL} MONGODB_URI="${MONGODB_URI}" .evergreen/run-tests-atlas-full.sh + - name: "test-faas-aws-lambda-task" + commands: + - command: ec2.assume_role + params: + role_arn: ${LAMBDA_AWS_ROLE_ARN} + duration_seconds: 3600 + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} + + TEST_LAMBDA_DIRECTORY="${PROJECT_DIRECTORY}/spec/support/faas/app/aws_lambda" \ + AWS_REGION="us-east-1" \ + MONGODB_URI="${MONGODB_URI}" \ + .evergreen/aws_lambda/run-deployed-lambda-aws-tests.sh - name: "testgcpkms-task" commands: - command: shell.exec diff --git a/Rakefile b/Rakefile index 4a4458070d..5aeb14f4b2 100644 --- a/Rakefile +++ b/Rakefile @@ -132,3 +132,29 @@ namespace :docs do end load 'profile/benchmarking/rake/tasks.rake' + +desc 'Build and validate the evergreen config' +task eg: %w[ eg:build eg:validate ] + +# 'eg' == 'evergreen', but evergreen is too many letters for convenience +namespace :eg do + desc 'Builds the .evergreen/config.yml file from the templates' + task :build do + ruby '.evergreen/update-evergreen-configs' + end + + desc 'Validates the .evergreen/config.yml file' + task :validate do + system 'evergreen validate --project mongo-ruby-driver .evergreen/config.yml' + end + + desc 'Updates the evergreen executable to the latest available version' + task :update do + system 'evergreen get-update --install' + end + + desc 'Runs the current branch as an evergreen patch' + task :patch do + system 'evergreen patch --uncommitted --project mongo-ruby-driver --browse --auto-description --yes' + end +end diff --git a/spec/support/faas/app/aws_lambda/Gemfile b/spec/support/faas/app/aws_lambda/Gemfile new file mode 100644 index 0000000000..b566789dfb --- /dev/null +++ b/spec/support/faas/app/aws_lambda/Gemfile @@ -0,0 +1,8 @@ +source "https://rubygems.org" + +gem "httparty" + +group :test do + gem "test-unit" + gem "mocha" +end diff --git a/spec/support/faas/app/aws_lambda/README.md b/spec/support/faas/app/aws_lambda/README.md new file mode 100644 index 0000000000..b6889edd83 --- /dev/null +++ b/spec/support/faas/app/aws_lambda/README.md @@ -0,0 +1,18 @@ +# MongoDB FaaS Test Application for AWS Lambda + +This folder contains source code and supporting files for a serverless test application to be run via AWS Lambda. + +For information about how this test application is intended to be used, please see: https://github.com/mongodb/specifications/blob/master/source/faas-automated-testing/faas-automated-testing.rst#implementing-automated-faas-tests + + +## Running Locally + +To run this locally, follow the instructions in the link above. If you aren't running an x86-64 architecture locally (e.g. Apple M1, etc.) you will need to bundle the gems via Docker so that gems with native components (e.g. BSON-Ruby) are built for the appropriate architecture. + +The included `bundle-gems.sh` script is intended to help with this. To use it, change to the `mongodb` subdirectory, and then invoke the helper script: + +~~~ +faas/app/aws_lambda/mongodb $ ../bundle-gems.sh +~~~ + +This will invoke `bundle install` via a Docker container and create a `vendor` subdirectory. Once that is done, return to the `faas/app/aws_lambda` folder and run `sam build` and `sam invoke` (as described in the `faas-automated-testing` specification). diff --git a/spec/support/faas/app/aws_lambda/bundle-gems.sh b/spec/support/faas/app/aws_lambda/bundle-gems.sh new file mode 100755 index 0000000000..1556d1b979 --- /dev/null +++ b/spec/support/faas/app/aws_lambda/bundle-gems.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# source: +# https://dev.to/aws-builders/building-aws-ruby-lambdas-that-require-gems-with-native-extension-17h + +dir=`pwd` + +bundle config set --local path 'vendor/bundle' +bundle config set --local deployment 'true' + +docker run --platform=linux/amd64 \ + -e BUNDLE_SILENCE_ROOT_WARNING=1 \ + -v $dir:$dir \ + -w $dir \ + public.ecr.aws/sam/build-ruby3.2 \ + bundle install diff --git a/spec/support/faas/app/aws_lambda/mongodb/Gemfile b/spec/support/faas/app/aws_lambda/mongodb/Gemfile new file mode 100644 index 0000000000..a1aa756f06 --- /dev/null +++ b/spec/support/faas/app/aws_lambda/mongodb/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "mongo" + +ruby '~> 3.2.0' diff --git a/spec/support/faas/app/aws_lambda/mongodb/app.rb b/spec/support/faas/app/aws_lambda/mongodb/app.rb new file mode 100644 index 0000000000..41746e457f --- /dev/null +++ b/spec/support/faas/app/aws_lambda/mongodb/app.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'mongo' +require 'faas_test/runner' + +# Helpful resources: +# https://dev.to/aws-builders/building-aws-ruby-lambdas-that-require-gems-with-native-extension-17h + +# Parameters +# ---------- +# event: Hash, required +# API Gateway Lambda Proxy Input Format +# Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format +# +# context: object, required +# Lambda Context runtime methods and attributes +# Context doc: https://docs.aws.amazon.com/lambda/latest/dg/ruby-context.html +def lambda_handler(event:, context:) + client = Mongo::Client.new(ENV['MONGODB_URI']) + runner = FaaSTest::Runner.new(client) + + results = runner.run + + { + statusCode: 200, + body: results.to_json + } +end diff --git a/spec/support/faas/app/aws_lambda/mongodb/faas_test/runner.rb b/spec/support/faas/app/aws_lambda/mongodb/faas_test/runner.rb new file mode 100644 index 0000000000..87a46ff1c3 --- /dev/null +++ b/spec/support/faas/app/aws_lambda/mongodb/faas_test/runner.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'faas_test/subscribers/heartbeat' +require 'faas_test/subscribers/command' +require 'faas_test/subscribers/connection_pool' + +module FaaSTest + class Runner + extend Forwardable + + attr_reader :client + attr_reader :collection_name + + def_delegators :client, :database + + def initialize(client) + @client = client + @collection_name = BSON::ObjectId.new.to_s + + prepare_subscriptions! + end + + def run + perform_test + compile_results + end + + private + + attr_reader :heartbeat_subscriber + attr_reader :command_subscriber + attr_reader :connection_pool_subscriber + + def compile_results + { + heartbeat: { + started: heartbeat_subscriber.started_count, + succeeded: heartbeat_subscriber.succeeded_count, + failed: heartbeat_subscriber.failed_count, + durations: heartbeat_subscriber.durations, + }, + command: { + durations: command_subscriber.durations, + }, + connections: { + open: connection_pool_subscriber.open_connections, + } + } + end + + def perform_test + result = database[collection_name].insert_one({ a: 1, b: '2' }) + id = result.inserted_id + + database[collection_name].delete_one(_id: id) + end + + def prepare_subscriptions! + @heartbeat_subscriber = FaaSTest::Subscribers::Heartbeat.new + @command_subscriber = FaaSTest::Subscribers::Command.new + @connection_pool_subscriber = FaaSTest::Subscribers::ConnectionPool.new + + client.subscribe(Mongo::Monitoring::SERVER_HEARTBEAT, heartbeat_subscriber) + client.subscribe(Mongo::Monitoring::COMMAND, command_subscriber) + client.subscribe(Mongo::Monitoring::CONNECTION_POOL, connection_pool_subscriber) + end + end +end diff --git a/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/command.rb b/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/command.rb new file mode 100644 index 0000000000..ae4f65af83 --- /dev/null +++ b/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/command.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module FaaSTest + module Subscribers + class Command + attr_reader :durations + + def initialize + @durations = [] + end + + def started(_event) + end + + def succeeded(event) + @durations.push event.duration + end + + def failed(_event) + end + end + end +end diff --git a/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/connection_pool.rb b/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/connection_pool.rb new file mode 100644 index 0000000000..d5de41ed2e --- /dev/null +++ b/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/connection_pool.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module FaaSTest + module Subscribers + class ConnectionPool + attr_reader :open_connections + + def initialize + @open_connections = 0 + end + + def published(event) + case event + when Mongo::Monitoring::Event::Cmap::ConnectionCreated + @open_connections += 1 + when Mongo::Monitoring::Event::Cmap::ConnectionClosed + @open_connections -= 1 + end + end + end + end +end diff --git a/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/heartbeat.rb b/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/heartbeat.rb new file mode 100644 index 0000000000..6310374756 --- /dev/null +++ b/spec/support/faas/app/aws_lambda/mongodb/faas_test/subscribers/heartbeat.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module FaaSTest + module Subscribers + class Heartbeat + attr_reader :durations + attr_reader :started_count, :succeeded_count, :failed_count + + def initialize + @durations = [] + @started_count = @succeeded_count = @failed_count = 0 + end + + def started(_event) + @started_count += 1 + end + + def succeeded(event) + @succeeded_count += 1 + @durations.push event.round_trip_time + end + + def failed(_event) + @failed_count += 1 + end + end + end +end diff --git a/spec/support/faas/app/aws_lambda/samconfig.toml b/spec/support/faas/app/aws_lambda/samconfig.toml new file mode 100644 index 0000000000..bb15bcb318 --- /dev/null +++ b/spec/support/faas/app/aws_lambda/samconfig.toml @@ -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 = "mongo-ruby" + +[default.build.parameters] +cached = true +parallel = true + +[default.validate.parameters] +lint = true + +[default.deploy.parameters] +capabilities = "CAPABILITY_IAM" +confirm_changeset = true +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" diff --git a/spec/support/faas/app/aws_lambda/template.yaml b/spec/support/faas/app/aws_lambda/template.yaml new file mode 100644 index 0000000000..2327add005 --- /dev/null +++ b/spec/support/faas/app/aws_lambda/template.yaml @@ -0,0 +1,47 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: FaaS Test Application for the MongoDB Ruby Driver + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 30 + MemorySize: 128 + +Parameters: + MongoDbUri: + Type: String + Description: The MongoDB connection string. + +Resources: + MongoDBFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: mongodb/ + Environment: + Variables: + MONGODB_URI: !Ref MongoDbUri + Handler: app.lambda_handler + Runtime: ruby3.2 + Architectures: + - x86_64 + Events: + MongoDB: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /mongodb + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + MongoDBApi: + Description: "API Gateway endpoint URL for Prod stage for MongoDB Ruby function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/mongodb/" + MongoDBFunction: + Description: "MongoDB Ruby Lambda Function ARN" + Value: !GetAtt MongoDBFunction.Arn + MongoDBFunctionIamRole: + Description: "Implicit IAM Role created for MongoDB Ruby function" + Value: !GetAtt MongoDBFunctionRole.Arn