From 8077964d286e38eb65a9eee4f6b2ea024921a028 Mon Sep 17 00:00:00 2001 From: Huy Do Date: Thu, 5 Jun 2025 16:38:29 -0700 Subject: [PATCH 1/8] Add a lambda to write benchmark results Signed-off-by: Huy Do --- .../workflows/lambda-do-release-runners.yml | 32 ++++ .../benchmark-results-uploader/Makefile | 18 +++ .../benchmark-results-uploader/README.md | 56 +++++++ .../lambda_function.py | 136 +++++++++++++++++ .../requirements.txt | 1 + .../test_lambda_function.py | 137 ++++++++++++++++++ 6 files changed, 380 insertions(+) create mode 100644 aws/lambda/benchmark-results-uploader/Makefile create mode 100644 aws/lambda/benchmark-results-uploader/README.md create mode 100644 aws/lambda/benchmark-results-uploader/lambda_function.py create mode 100644 aws/lambda/benchmark-results-uploader/requirements.txt create mode 100644 aws/lambda/benchmark-results-uploader/test_lambda_function.py diff --git a/.github/workflows/lambda-do-release-runners.yml b/.github/workflows/lambda-do-release-runners.yml index 7916073b92..05e06f8232 100644 --- a/.github/workflows/lambda-do-release-runners.yml +++ b/.github/workflows/lambda-do-release-runners.yml @@ -179,6 +179,38 @@ jobs: tag: ${{ inputs.tag }} updateOnlyUnreleased: true + release-benchmark-results-uploader: + name: Upload Release for benchmark-results-uploader lambda + runs-on: ubuntu-latest + permissions: + contents: write + env: + REF: ${{ inputs.tag }} + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.tag }} + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.12' + + - name: Build deployment.zip + working-directory: aws/lambda/benchmark-results-uploader + run: make deployment.zip + + - name: Copy deployment.zip to root + run: cp aws/lambda/benchmark-results-uploader/deployment.zip benchmark-results-uploader.zip + + - uses: ncipollo/release-action@v1 + with: + artifacts: "benchmark-results-uploader.zip" + allowUpdates: true + draft: true + name: ${{ inputs.tag }} + tag: ${{ inputs.tag }} + updateOnlyUnreleased: true finish-release: needs: diff --git a/aws/lambda/benchmark-results-uploader/Makefile b/aws/lambda/benchmark-results-uploader/Makefile new file mode 100644 index 0000000000..55b004db5d --- /dev/null +++ b/aws/lambda/benchmark-results-uploader/Makefile @@ -0,0 +1,18 @@ +FUNCTION_NAME=benchmark-results-uploader +PROJECT_NAME=pytorch +REGION=us-east-1 + +.PHONY: prepare deploy clean + +prepare: + rm -rf ./packages + mkdir -p packages + pip install -r requirements.txt -t packages + cd packages && zip -r9 ../function.zip . + zip -g function.zip lambda_function.py + +deploy: prepare + aws lambda update-function-code --function-name $(PROJECT_NAME)-$(FUNCTION_NAME) --zip-file fileb://function.zip --region $(REGION) + +clean: + rm -rf packages function.zip \ No newline at end of file diff --git a/aws/lambda/benchmark-results-uploader/README.md b/aws/lambda/benchmark-results-uploader/README.md new file mode 100644 index 0000000000..b38ac58980 --- /dev/null +++ b/aws/lambda/benchmark-results-uploader/README.md @@ -0,0 +1,56 @@ +# Benchmark Results Uploader + +This AWS Lambda function uploads benchmark result files to S3 buckets with authentication. + +## Functionality + +This Lambda: + +1. Accepts S3 bucket name, path, and content of a file +2. Authenticates the request using username/password from environment variables +3. Checks if the specified path already exists in the S3 bucket +4. If the path doesn't exist, uploads the content to that path +5. Returns appropriate HTTP status codes and messages + +## Input Parameters + +The Lambda expects the following input parameters in the event object: + +- `username`: Username for authentication +- `password`: Password for authentication +- `bucket_name`: Name of the S3 bucket +- `path`: Path within the bucket where content will be stored +- `content`: The content to upload + +## Environment Variables + +The Lambda requires two environment variables: + +- `AUTH_USERNAME`: Username for authentication +- `AUTH_PASSWORD`: Password for authentication + +## Deployment + +To deploy the Lambda function: + +```bash +make deploy +``` + +This will: +1. Install dependencies +2. Package the Lambda function +3. Deploy to AWS + +## Testing + +To test the Lambda function locally: + +```bash +# Setup environment variables +export AUTH_USERNAME=your_username +export AUTH_PASSWORD=your_password + +# Run test +python test_lambda_function.py +``` diff --git a/aws/lambda/benchmark-results-uploader/lambda_function.py b/aws/lambda/benchmark-results-uploader/lambda_function.py new file mode 100644 index 0000000000..f36819bc70 --- /dev/null +++ b/aws/lambda/benchmark-results-uploader/lambda_function.py @@ -0,0 +1,136 @@ +import os +import json +import boto3 +from botocore.exceptions import ClientError +from typing import Dict, Any + +# Configure AWS S3 client +s3_client = boto3.client("s3") + + +def authenticate(username: str, password: str) -> bool: + """ + Authenticate request using environment variable credentials. + + Args: + username (str): Username provided in the request + password (str): Password provided in the request + + Returns: + bool: True if authentication is successful, False otherwise + """ + return username == os.environ.get("AUTH_USERNAME") and password == os.environ.get( + "AUTH_PASSWORD" + ) + + +def check_path_exists(bucket: str, path: str) -> bool: + """ + Check if a specific path exists in the S3 bucket. + + Args: + bucket (str): The name of the S3 bucket + path (str): The path to check within the bucket + + Returns: + bool: True if the path exists, False otherwise + """ + try: + s3_client.head_object(Bucket=bucket, Key=path) + return True + except ClientError as e: + # If the error code is 404, the path doesn't exist + if e.response["Error"]["Code"] == "404": + return False + # For other errors, raise the exception + raise + + +def upload_to_s3(bucket: str, path: str, content: str) -> Dict[str, Any]: + """ + Upload content to a specific path in the S3 bucket. + + Args: + bucket (str): The name of the S3 bucket + path (str): The path within the bucket where content will be stored + content (str): The content to upload + + Returns: + Dict[str, Any]: Response from S3 upload + """ + try: + response = s3_client.put_object( + Bucket=bucket, Key=path, Body=content, ContentType="application/json" + ) + return { + "statusCode": 200, + "body": json.dumps( + { + "message": f"File uploaded successfully to {bucket}/{path}", + "etag": response.get("ETag", ""), + } + ), + } + except Exception as e: + return { + "statusCode": 500, + "body": json.dumps({"message": f"Error uploading file: {str(e)}"}), + } + + +def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: + """ + Main Lambda handler function. + + Args: + event (Dict[str, Any]): Contains input data for the Lambda function + Required fields: + - bucket_name: The name of the S3 bucket + - path: The path within the bucket where content will be stored + - content: The content to upload + - username: Username for authentication + - password: Password for authentication + context (Any): Provides runtime information about the Lambda function + + Returns: + Dict[str, Any]: Response containing status and result information + """ + # Extract authentication parameters + try: + username = event["username"] + password = event["password"] + except KeyError: + return { + "statusCode": 401, + "body": json.dumps({"message": "Authentication credentials are required"}), + } + + # Validate authentication + if not authenticate(username, password): + return { + "statusCode": 403, + "body": json.dumps({"message": "Invalid authentication credentials"}), + } + + # Extract input parameters from the event + try: + bucket_name = event["bucket_name"] + path = event["path"] + content = event["content"] + except KeyError as e: + return { + "statusCode": 400, + "body": json.dumps({"message": f"Missing required parameter: {str(e)}"}), + } + + # Check if the path already exists in the bucket + if check_path_exists(bucket_name, path): + return { + "statusCode": 409, # Conflict status code + "body": json.dumps( + {"message": f"Path {path} already exists in bucket {bucket_name}"} + ), + } + + # Upload the content to S3 + return upload_to_s3(bucket_name, path, content) diff --git a/aws/lambda/benchmark-results-uploader/requirements.txt b/aws/lambda/benchmark-results-uploader/requirements.txt new file mode 100644 index 0000000000..7e78fe8858 --- /dev/null +++ b/aws/lambda/benchmark-results-uploader/requirements.txt @@ -0,0 +1 @@ +boto3==1.36.21 diff --git a/aws/lambda/benchmark-results-uploader/test_lambda_function.py b/aws/lambda/benchmark-results-uploader/test_lambda_function.py new file mode 100644 index 0000000000..7d45bd4fb6 --- /dev/null +++ b/aws/lambda/benchmark-results-uploader/test_lambda_function.py @@ -0,0 +1,137 @@ +import os +import unittest +import json +from unittest.mock import patch +from botocore.exceptions import ClientError +from lambda_function import ( + lambda_handler, + check_path_exists, + upload_to_s3, + authenticate, +) + + +class TestBenchmarkResultsUploader(unittest.TestCase): + def setUp(self): + # Set up test environment variables + os.environ["AUTH_USERNAME"] = "test_user" + os.environ["AUTH_PASSWORD"] = "test_password" + + # Test event with valid credentials + self.valid_event = { + "username": "test_user", + "password": "test_password", + "bucket_name": "test-bucket", + "path": "test/path.json", + "content": '{"test": "data"}', + } + + # Test event with invalid credentials + self.invalid_auth_event = { + "username": "wrong_user", + "password": "wrong_password", + "bucket_name": "test-bucket", + "path": "test/path.json", + "content": '{"test": "data"}', + } + + # Test event missing required fields + self.incomplete_event = { + "username": "test_user", + "password": "test_password", + "bucket_name": "test-bucket", + } + + @patch("lambda_function.authenticate") + def test_authentication_failure(self, mock_authenticate): + mock_authenticate.return_value = False + response = lambda_handler(self.invalid_auth_event, {}) + self.assertEqual(response["statusCode"], 403) + self.assertIn( + "Invalid authentication credentials", + json.loads(response["body"])["message"], + ) + + @patch("lambda_function.authenticate") + @patch("lambda_function.check_path_exists") + @patch("lambda_function.upload_to_s3") + def test_successful_upload( + self, mock_upload_to_s3, mock_check_path_exists, mock_authenticate + ): + mock_authenticate.return_value = True + mock_check_path_exists.return_value = False + + expected_response = { + "statusCode": 200, + "body": json.dumps( + { + "message": "File uploaded successfully to test-bucket/test/path.json", + "etag": "test-etag", + } + ), + } + mock_upload_to_s3.return_value = expected_response + + response = lambda_handler(self.valid_event, {}) + self.assertEqual(response["statusCode"], 200) + mock_upload_to_s3.assert_called_once_with( + "test-bucket", "test/path.json", '{"test": "data"}' + ) + + @patch("lambda_function.authenticate") + @patch("lambda_function.check_path_exists") + def test_path_already_exists(self, mock_check_path_exists, mock_authenticate): + mock_authenticate.return_value = True + mock_check_path_exists.return_value = True + + response = lambda_handler(self.valid_event, {}) + self.assertEqual(response["statusCode"], 409) + self.assertIn("already exists", json.loads(response["body"])["message"]) + + @patch("lambda_function.authenticate") + def test_missing_parameters(self, mock_authenticate): + mock_authenticate.return_value = True + + response = lambda_handler(self.incomplete_event, {}) + self.assertEqual(response["statusCode"], 400) + self.assertIn( + "Missing required parameter", json.loads(response["body"])["message"] + ) + + @patch("lambda_function.s3_client") + def test_check_path_exists_true(self, mock_s3_client): + mock_s3_client.head_object.return_value = {} + self.assertTrue(check_path_exists("test-bucket", "test/path.json")) + + @patch("lambda_function.s3_client") + def test_check_path_exists_false(self, mock_s3_client): + error_response = {"Error": {"Code": "404"}} + mock_s3_client.head_object.side_effect = ClientError( + error_response, "HeadObject" + ) + self.assertFalse(check_path_exists("test-bucket", "test/path.json")) + + @patch("lambda_function.s3_client") + def test_upload_to_s3_success(self, mock_s3_client): + mock_s3_client.put_object.return_value = {"ETag": "test-etag"} + response = upload_to_s3("test-bucket", "test/path.json", '{"test": "data"}') + self.assertEqual(response["statusCode"], 200) + body = json.loads(response["body"]) + self.assertIn("File uploaded successfully", body["message"]) + self.assertEqual(body["etag"], "test-etag") + + @patch("lambda_function.s3_client") + def test_upload_to_s3_failure(self, mock_s3_client): + mock_s3_client.put_object.side_effect = Exception("Test error") + response = upload_to_s3("test-bucket", "test/path.json", '{"test": "data"}') + self.assertEqual(response["statusCode"], 500) + self.assertIn("Error uploading file", json.loads(response["body"])["message"]) + + def test_authenticate(self): + self.assertTrue(authenticate("test_user", "test_password")) + self.assertFalse(authenticate("wrong_user", "test_password")) + self.assertFalse(authenticate("test_user", "wrong_password")) + + +if __name__ == "__main__": + unittest.main() From f53fb99d5b11aa9bdf96b77fd7ea317403a90a5b Mon Sep 17 00:00:00 2001 From: Huy Do Date: Thu, 5 Jun 2025 17:41:39 -0700 Subject: [PATCH 2/8] Hardcode the bucket for now Signed-off-by: Huy Do --- .../benchmark-results-uploader/Makefile | 2 +- .../benchmark-results-uploader/README.md | 2 +- .../lambda_function.py | 30 ++++++++++--------- .../test_lambda_function.py | 23 ++++++-------- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/aws/lambda/benchmark-results-uploader/Makefile b/aws/lambda/benchmark-results-uploader/Makefile index 55b004db5d..2735fadeac 100644 --- a/aws/lambda/benchmark-results-uploader/Makefile +++ b/aws/lambda/benchmark-results-uploader/Makefile @@ -15,4 +15,4 @@ deploy: prepare aws lambda update-function-code --function-name $(PROJECT_NAME)-$(FUNCTION_NAME) --zip-file fileb://function.zip --region $(REGION) clean: - rm -rf packages function.zip \ No newline at end of file + rm -rf packages function.zip diff --git a/aws/lambda/benchmark-results-uploader/README.md b/aws/lambda/benchmark-results-uploader/README.md index b38ac58980..496fa8742f 100644 --- a/aws/lambda/benchmark-results-uploader/README.md +++ b/aws/lambda/benchmark-results-uploader/README.md @@ -7,7 +7,7 @@ This AWS Lambda function uploads benchmark result files to S3 buckets with authe This Lambda: 1. Accepts S3 bucket name, path, and content of a file -2. Authenticates the request using username/password from environment variables +2. Authenticates the request using username/password from environment variables 3. Checks if the specified path already exists in the S3 bucket 4. If the path doesn't exist, uploads the content to that path 5. Returns appropriate HTTP status codes and messages diff --git a/aws/lambda/benchmark-results-uploader/lambda_function.py b/aws/lambda/benchmark-results-uploader/lambda_function.py index f36819bc70..404fd1aa7a 100644 --- a/aws/lambda/benchmark-results-uploader/lambda_function.py +++ b/aws/lambda/benchmark-results-uploader/lambda_function.py @@ -5,7 +5,8 @@ from typing import Dict, Any # Configure AWS S3 client -s3_client = boto3.client("s3") +S3_CLIENT = boto3.client("s3") +OSSCI_BENCHMARKS_BUCKET = "ossci-benchmarks" def authenticate(username: str, password: str) -> bool: @@ -24,19 +25,18 @@ def authenticate(username: str, password: str) -> bool: ) -def check_path_exists(bucket: str, path: str) -> bool: +def check_path_exists(path: str) -> bool: """ Check if a specific path exists in the S3 bucket. Args: - bucket (str): The name of the S3 bucket path (str): The path to check within the bucket Returns: bool: True if the path exists, False otherwise """ try: - s3_client.head_object(Bucket=bucket, Key=path) + S3_CLIENT.head_object(Bucket=OSSCI_BENCHMARKS_BUCKET, Key=path) return True except ClientError as e: # If the error code is 404, the path doesn't exist @@ -46,12 +46,11 @@ def check_path_exists(bucket: str, path: str) -> bool: raise -def upload_to_s3(bucket: str, path: str, content: str) -> Dict[str, Any]: +def upload_to_s3(path: str, content: str) -> Dict[str, Any]: """ Upload content to a specific path in the S3 bucket. Args: - bucket (str): The name of the S3 bucket path (str): The path within the bucket where content will be stored content (str): The content to upload @@ -59,14 +58,17 @@ def upload_to_s3(bucket: str, path: str, content: str) -> Dict[str, Any]: Dict[str, Any]: Response from S3 upload """ try: - response = s3_client.put_object( - Bucket=bucket, Key=path, Body=content, ContentType="application/json" + response = S3_CLIENT.put_object( + Bucket=OSSCI_BENCHMARKS_BUCKET, + Key=path, + Body=content, + ContentType="application/json", ) return { "statusCode": 200, "body": json.dumps( { - "message": f"File uploaded successfully to {bucket}/{path}", + "message": f"File uploaded successfully to {OSSCI_BENCHMARKS_BUCKET}/{path}", "etag": response.get("ETag", ""), } ), @@ -85,7 +87,6 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: Args: event (Dict[str, Any]): Contains input data for the Lambda function Required fields: - - bucket_name: The name of the S3 bucket - path: The path within the bucket where content will be stored - content: The content to upload - username: Username for authentication @@ -114,7 +115,6 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: # Extract input parameters from the event try: - bucket_name = event["bucket_name"] path = event["path"] content = event["content"] except KeyError as e: @@ -124,13 +124,15 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: } # Check if the path already exists in the bucket - if check_path_exists(bucket_name, path): + if check_path_exists(path): return { "statusCode": 409, # Conflict status code "body": json.dumps( - {"message": f"Path {path} already exists in bucket {bucket_name}"} + { + "message": f"Path {path} already exists in bucket {OSSCI_BENCHMARKS_BUCKET}" + } ), } # Upload the content to S3 - return upload_to_s3(bucket_name, path, content) + return upload_to_s3(path, content) diff --git a/aws/lambda/benchmark-results-uploader/test_lambda_function.py b/aws/lambda/benchmark-results-uploader/test_lambda_function.py index 7d45bd4fb6..5315a50d5b 100644 --- a/aws/lambda/benchmark-results-uploader/test_lambda_function.py +++ b/aws/lambda/benchmark-results-uploader/test_lambda_function.py @@ -21,7 +21,6 @@ def setUp(self): self.valid_event = { "username": "test_user", "password": "test_password", - "bucket_name": "test-bucket", "path": "test/path.json", "content": '{"test": "data"}', } @@ -30,7 +29,6 @@ def setUp(self): self.invalid_auth_event = { "username": "wrong_user", "password": "wrong_password", - "bucket_name": "test-bucket", "path": "test/path.json", "content": '{"test": "data"}', } @@ -39,7 +37,6 @@ def setUp(self): self.incomplete_event = { "username": "test_user", "password": "test_password", - "bucket_name": "test-bucket", } @patch("lambda_function.authenticate") @@ -74,9 +71,7 @@ def test_successful_upload( response = lambda_handler(self.valid_event, {}) self.assertEqual(response["statusCode"], 200) - mock_upload_to_s3.assert_called_once_with( - "test-bucket", "test/path.json", '{"test": "data"}' - ) + mock_upload_to_s3.assert_called_once_with("test/path.json", '{"test": "data"}') @patch("lambda_function.authenticate") @patch("lambda_function.check_path_exists") @@ -98,32 +93,32 @@ def test_missing_parameters(self, mock_authenticate): "Missing required parameter", json.loads(response["body"])["message"] ) - @patch("lambda_function.s3_client") + @patch("lambda_function.S3_CLIENT") def test_check_path_exists_true(self, mock_s3_client): mock_s3_client.head_object.return_value = {} - self.assertTrue(check_path_exists("test-bucket", "test/path.json")) + self.assertTrue(check_path_exists("test/path.json")) - @patch("lambda_function.s3_client") + @patch("lambda_function.S3_CLIENT") def test_check_path_exists_false(self, mock_s3_client): error_response = {"Error": {"Code": "404"}} mock_s3_client.head_object.side_effect = ClientError( error_response, "HeadObject" ) - self.assertFalse(check_path_exists("test-bucket", "test/path.json")) + self.assertFalse(check_path_exists("test/path.json")) - @patch("lambda_function.s3_client") + @patch("lambda_function.S3_CLIENT") def test_upload_to_s3_success(self, mock_s3_client): mock_s3_client.put_object.return_value = {"ETag": "test-etag"} - response = upload_to_s3("test-bucket", "test/path.json", '{"test": "data"}') + response = upload_to_s3("test/path.json", '{"test": "data"}') self.assertEqual(response["statusCode"], 200) body = json.loads(response["body"]) self.assertIn("File uploaded successfully", body["message"]) self.assertEqual(body["etag"], "test-etag") - @patch("lambda_function.s3_client") + @patch("lambda_function.S3_CLIENT") def test_upload_to_s3_failure(self, mock_s3_client): mock_s3_client.put_object.side_effect = Exception("Test error") - response = upload_to_s3("test-bucket", "test/path.json", '{"test": "data"}') + response = upload_to_s3("test/path.json", '{"test": "data"}') self.assertEqual(response["statusCode"], 500) self.assertIn("Error uploading file", json.loads(response["body"])["message"]) From 8a0a3670aa75e2108ad4d1368a2d47b08889e0a9 Mon Sep 17 00:00:00 2001 From: Huy Do Date: Thu, 5 Jun 2025 19:12:21 -0700 Subject: [PATCH 3/8] Kind of ready now Signed-off-by: Huy Do --- .../benchmark-results-uploader/Makefile | 29 +++++++++-------- .../lambda_function.py | 26 ++++++++++++--- .../test_lambda_function.py | 32 +++++++++++++------ 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/aws/lambda/benchmark-results-uploader/Makefile b/aws/lambda/benchmark-results-uploader/Makefile index 2735fadeac..478548770a 100644 --- a/aws/lambda/benchmark-results-uploader/Makefile +++ b/aws/lambda/benchmark-results-uploader/Makefile @@ -1,18 +1,19 @@ -FUNCTION_NAME=benchmark-results-uploader -PROJECT_NAME=pytorch -REGION=us-east-1 +all: run-local -.PHONY: prepare deploy clean +clean: + rm -rf deployment + rm -rf venv + rm -rf deployment.zip -prepare: - rm -rf ./packages - mkdir -p packages - pip install -r requirements.txt -t packages - cd packages && zip -r9 ../function.zip . - zip -g function.zip lambda_function.py +venv/bin/python: + virtualenv venv + venv/bin/pip install -r requirements.txt -deploy: prepare - aws lambda update-function-code --function-name $(PROJECT_NAME)-$(FUNCTION_NAME) --zip-file fileb://function.zip --region $(REGION) +deployment.zip: + mkdir -p deployment + cp lambda_function.py ./deployment/. + pip3.10 install -r requirements.txt -t ./deployment/. --platform manylinux2014_x86_64 --only-binary=:all: --implementation cp --python-version 3.10 --upgrade + cd ./deployment && zip -q -r ../deployment.zip . -clean: - rm -rf packages function.zip +.PHONY: create-deployment-package +create-deployment-package: deployment.zip diff --git a/aws/lambda/benchmark-results-uploader/lambda_function.py b/aws/lambda/benchmark-results-uploader/lambda_function.py index 404fd1aa7a..f8e974636f 100644 --- a/aws/lambda/benchmark-results-uploader/lambda_function.py +++ b/aws/lambda/benchmark-results-uploader/lambda_function.py @@ -3,6 +3,7 @@ import boto3 from botocore.exceptions import ClientError from typing import Dict, Any +from json.decoder import JSONDecodeError # Configure AWS S3 client S3_CLIENT = boto3.client("s3") @@ -96,10 +97,27 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: Returns: Dict[str, Any]: Response containing status and result information """ + body = event["body"] + if not body: + return { + "statusCode": 400, + "body": json.dumps({"message": "Missing json request body"}), + } + + try: + parsed_body = json.loads(body) + except JSONDecodeError as e: + return { + "statusCode": 400, + "body": json.dumps( + {"message": f"Cannot parse json request body: {str(e)}"} + ), + } + # Extract authentication parameters try: - username = event["username"] - password = event["password"] + username = parsed_body["username"] + password = parsed_body["password"] except KeyError: return { "statusCode": 401, @@ -115,8 +133,8 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: # Extract input parameters from the event try: - path = event["path"] - content = event["content"] + path = parsed_body["path"] + content = parsed_body["content"] except KeyError as e: return { "statusCode": 400, diff --git a/aws/lambda/benchmark-results-uploader/test_lambda_function.py b/aws/lambda/benchmark-results-uploader/test_lambda_function.py index 5315a50d5b..e275f4e52a 100644 --- a/aws/lambda/benchmark-results-uploader/test_lambda_function.py +++ b/aws/lambda/benchmark-results-uploader/test_lambda_function.py @@ -19,24 +19,36 @@ def setUp(self): # Test event with valid credentials self.valid_event = { - "username": "test_user", - "password": "test_password", - "path": "test/path.json", - "content": '{"test": "data"}', + "body": json.dumps( + { + "username": "test_user", + "password": "test_password", + "path": "test/path.json", + "content": '{"test": "data"}', + } + ) } # Test event with invalid credentials self.invalid_auth_event = { - "username": "wrong_user", - "password": "wrong_password", - "path": "test/path.json", - "content": '{"test": "data"}', + "body": json.dumps( + { + "username": "wrong_user", + "password": "wrong_password", + "path": "test/path.json", + "content": '{"test": "data"}', + } + ) } # Test event missing required fields self.incomplete_event = { - "username": "test_user", - "password": "test_password", + "body": json.dumps( + { + "username": "test_user", + "password": "test_password", + } + ) } @patch("lambda_function.authenticate") From fabf27e9a84a3ab922036b7ff992d15e6f29ec8f Mon Sep 17 00:00:00 2001 From: Huy Do Date: Thu, 5 Jun 2025 19:18:15 -0700 Subject: [PATCH 4/8] Just use 3.10 Signed-off-by: Huy Do --- .github/workflows/lambda-do-release-runners.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lambda-do-release-runners.yml b/.github/workflows/lambda-do-release-runners.yml index 05e06f8232..72c49850dc 100644 --- a/.github/workflows/lambda-do-release-runners.yml +++ b/.github/workflows/lambda-do-release-runners.yml @@ -194,7 +194,7 @@ jobs: - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: - python-version: '3.12' + python-version: '3.10' - name: Build deployment.zip working-directory: aws/lambda/benchmark-results-uploader From 1f2a5f3fdc6d176c76c9774f42c4e54662a23d87 Mon Sep 17 00:00:00 2001 From: Huy Do Date: Thu, 5 Jun 2025 19:28:34 -0700 Subject: [PATCH 5/8] Not sure what else is missing Signed-off-by: Huy Do --- .github/workflows/lambda-do-release-runners.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lambda-do-release-runners.yml b/.github/workflows/lambda-do-release-runners.yml index 72c49850dc..d6c4bb43fc 100644 --- a/.github/workflows/lambda-do-release-runners.yml +++ b/.github/workflows/lambda-do-release-runners.yml @@ -217,6 +217,7 @@ jobs: - release-lambdas - release-ci-queue-pct - release-oss-ci-job-queue-time + - release-benchmark-results-uploader name: Mark the release as final and publish it runs-on: ubuntu-latest permissions: From 78dd932d507c0ebd6196a9e870ce14747ec98ae1 Mon Sep 17 00:00:00 2001 From: Huy Do Date: Thu, 5 Jun 2025 19:41:45 -0700 Subject: [PATCH 6/8] Minor tweak Signed-off-by: Huy Do --- .../lambda_function.py | 16 ++++++++-------- .../test_lambda_function.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/aws/lambda/benchmark-results-uploader/lambda_function.py b/aws/lambda/benchmark-results-uploader/lambda_function.py index f8e974636f..83e55a97c3 100644 --- a/aws/lambda/benchmark-results-uploader/lambda_function.py +++ b/aws/lambda/benchmark-results-uploader/lambda_function.py @@ -21,9 +21,9 @@ def authenticate(username: str, password: str) -> bool: Returns: bool: True if authentication is successful, False otherwise """ - return username == os.environ.get("AUTH_USERNAME") and password == os.environ.get( - "AUTH_PASSWORD" - ) + return username == os.environ.get( + "UPLOADER_USERNAME" + ) and password == os.environ.get("UPLOADER_PASSWORD") def check_path_exists(path: str) -> bool: @@ -88,7 +88,7 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: Args: event (Dict[str, Any]): Contains input data for the Lambda function Required fields: - - path: The path within the bucket where content will be stored + - s3_path: The path within the bucket where content will be stored - content: The content to upload - username: Username for authentication - password: Password for authentication @@ -133,7 +133,7 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: # Extract input parameters from the event try: - path = parsed_body["path"] + s3_path = parsed_body["s3_path"] content = parsed_body["content"] except KeyError as e: return { @@ -142,15 +142,15 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: } # Check if the path already exists in the bucket - if check_path_exists(path): + if check_path_exists(s3_path): return { "statusCode": 409, # Conflict status code "body": json.dumps( { - "message": f"Path {path} already exists in bucket {OSSCI_BENCHMARKS_BUCKET}" + "message": f"Path {s3_path} already exists in bucket {OSSCI_BENCHMARKS_BUCKET}" } ), } # Upload the content to S3 - return upload_to_s3(path, content) + return upload_to_s3(s3_path, content) diff --git a/aws/lambda/benchmark-results-uploader/test_lambda_function.py b/aws/lambda/benchmark-results-uploader/test_lambda_function.py index e275f4e52a..bce7170041 100644 --- a/aws/lambda/benchmark-results-uploader/test_lambda_function.py +++ b/aws/lambda/benchmark-results-uploader/test_lambda_function.py @@ -14,8 +14,8 @@ class TestBenchmarkResultsUploader(unittest.TestCase): def setUp(self): # Set up test environment variables - os.environ["AUTH_USERNAME"] = "test_user" - os.environ["AUTH_PASSWORD"] = "test_password" + os.environ["UPLOADER_USERNAME"] = "test_user" + os.environ["UPLOADER_PASSWORD"] = "test_password" # Test event with valid credentials self.valid_event = { @@ -23,7 +23,7 @@ def setUp(self): { "username": "test_user", "password": "test_password", - "path": "test/path.json", + "s3_path": "test/path.json", "content": '{"test": "data"}', } ) @@ -35,7 +35,7 @@ def setUp(self): { "username": "wrong_user", "password": "wrong_password", - "path": "test/path.json", + "s3_path": "test/path.json", "content": '{"test": "data"}', } ) From 4fe4a9a221c2f15df595da35ec4e8f34c2f612d0 Mon Sep 17 00:00:00 2001 From: Huy Do Date: Fri, 6 Jun 2025 13:06:07 -0700 Subject: [PATCH 7/8] Clean up some redundant comments Signed-off-by: Huy Do --- aws/lambda/benchmark-results-uploader/lambda_function.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/aws/lambda/benchmark-results-uploader/lambda_function.py b/aws/lambda/benchmark-results-uploader/lambda_function.py index 83e55a97c3..8a9ee47677 100644 --- a/aws/lambda/benchmark-results-uploader/lambda_function.py +++ b/aws/lambda/benchmark-results-uploader/lambda_function.py @@ -114,7 +114,6 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: ), } - # Extract authentication parameters try: username = parsed_body["username"] password = parsed_body["password"] @@ -124,14 +123,12 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: "body": json.dumps({"message": "Authentication credentials are required"}), } - # Validate authentication if not authenticate(username, password): return { "statusCode": 403, "body": json.dumps({"message": "Invalid authentication credentials"}), } - # Extract input parameters from the event try: s3_path = parsed_body["s3_path"] content = parsed_body["content"] @@ -141,7 +138,6 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: "body": json.dumps({"message": f"Missing required parameter: {str(e)}"}), } - # Check if the path already exists in the bucket if check_path_exists(s3_path): return { "statusCode": 409, # Conflict status code @@ -152,5 +148,4 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: ), } - # Upload the content to S3 return upload_to_s3(s3_path, content) From 8ea18009a23244e99c77e86b650f280d5d60b939 Mon Sep 17 00:00:00 2001 From: Huy Do Date: Fri, 6 Jun 2025 13:20:49 -0700 Subject: [PATCH 8/8] Fix lint Signed-off-by: Huy Do --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d3cb180572..72dce58494 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: - name: Install Lintrunner run: | - pip install lintrunner==0.12.5 + pip install lintrunner==0.12.5 boto3-stubs==1.34.51 lintrunner init - name: Run lintrunner on all files - Linux