Skip to content

Commit

Permalink
raise error when no regex matches url payload
Browse files Browse the repository at this point in the history
- use tempfile for lambda handler configuration
- use python docker to run lambda package build
- add unit tests to assert exception raised when no regex matches
  • Loading branch information
pymonger committed May 21, 2024
1 parent b591be1 commit eafec43
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 28 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies = [
"moto[all]~=5.0.6",
"httpx~=0.27.0",
"respx~=0.21.1",
"docker~=7.0.0",
# TODO: remove cryptography pin when this issue resolved:
# cryptography/hazmat/bindings/_rust.abi3.so: cannot open shared object file: No such file or directory
"cryptography<35.0.0",
Expand Down
10 changes: 8 additions & 2 deletions src/unity_initiator/cloud/lambda_handler.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import os
from tempfile import mkstemp

from ..router import Router
from ..utils.logger import logger


def lambda_handler(event, context):
logger.info("context: %s", context)

# TODO: Should use either AppConfig or retrieve router config from S3 location.
# For now, reading router config body from ROUTER_CFG env variable then writing
# to local file.
router_cfg = os.environ["ROUTER_CFG"]
router_file = "/tmp/router.yaml"
with open(router_file, "w", encoding="utf-8") as f:
fd, router_file = mkstemp(prefix="router_", suffix=".yaml", text=True)
with os.fdopen(fd, "w") as f:
f.write(router_cfg)
router = Router(router_file)
os.unlink(router_file)
return router.execute_actions(event["payload"])
8 changes: 8 additions & 0 deletions src/unity_initiator/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
from .utils.logger import logger


class NoEvaluatorRegexMatched(Exception):
pass


class Router:
def __init__(self, config_file):
self._config_file = config_file
self._config = YamlConf(self._config_file)

def get_evaluators_by_url(self, url):
found_match = False
for url_cfg in (
self._config.get("initiator_config").get("payload_type").get("url", [])
):
Expand All @@ -20,12 +25,15 @@ def get_evaluators_by_url(self, url):
match = regex.search(url)
logger.debug("match: %s", match)
if match:
found_match = True
for eval_cfg in url_cfg.get("evaluators", []):
logger.debug(
"eval_cfg: %s",
json.dumps(eval_cfg, cls=YamlConfEncoder, indent=2),
)
yield Evaluator(eval_cfg, url, match.groupdict())
if not found_match:
raise NoEvaluatorRegexMatched(f"No regex matched url {url}.")

async def resolve_async_actions(self, url):
return await asyncio.gather(
Expand Down
60 changes: 35 additions & 25 deletions tests/test_lambda.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import json
import os
import subprocess
from importlib.metadata import version
from uuid import uuid4

import boto3
import docker
import pytest
import respx
from botocore.exceptions import ClientError
Expand Down Expand Up @@ -81,19 +81,22 @@ def build_mock_lambda_package():
"""Build the mock lambda package."""

build_lambda_script = files("scripts").joinpath("build_mock_lambda_package.sh")
logger.info("build_lambda_script: %s", build_lambda_script)
proc = subprocess.run(
[
"docker",
"run",
"--rm",
"-v",
f"{os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))}:/var/task",
"mlupin/docker-lambda:python3.9-build",
"./scripts/build_mock_lambda_package.sh",
]
logger.info(
"Running build_lambda_script: %s\nThis may take some time...",
build_lambda_script,
)
client = docker.from_env()
client.containers.run(
"mlupin/docker-lambda:python3.9-build",
"./scripts/build_mock_lambda_package.sh",
auto_remove=True,
volumes={
f"{os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))}": {
"bind": "/var/task",
"mode": "rw",
}
},
)
assert proc.returncode == 0


def get_lambda_code():
Expand All @@ -114,9 +117,13 @@ def setup_class(cls):
cls.mock.start()
cls.client = boto3.client("lambda")
cls.function_name = str(uuid4())[0:6]

# TODO: Should use either AppConfig or retrieve router config from S3 location.
# For now, writing out router config body to env variable to pass to lambda.
cls.router_file = files("tests.resources").joinpath("test_router.yaml")
with open(cls.router_file) as f:
cls.router_cfg = f.read()

cls.fxn = cls.client.create_function(
FunctionName=cls.function_name,
Runtime="python3.11",
Expand Down Expand Up @@ -204,15 +211,18 @@ def test_invoke_function_all_test_cases(self):
for res in results:
assert res["success"]

# def test_invoke_function_unrecognized(self):
# """Test invocations of the router lambda using an unrecognized url."""

# in_data = {"payload": "s3://bucket/prefix/SWOTdata.nc"}
# invoke_res = self.client.invoke(
# FunctionName=self.function_name, InvocationType="Event", Payload=json.dumps(in_data)
# )
# logger.info("invoke_res: %s", invoke_res)
# results = json.loads(invoke_res["Payload"].read().decode("utf-8"))
# logger.info("results: %s", results)
# for res in results:
# assert res["success"] == False
def test_invoke_function_unrecognized(self):
"""Test invocations of the router lambda using an unrecognized url."""

in_data = {
"payload": "s3://bucket/prefix/NISAR_L0_PR_RRSD_063_136_A_129S_20240120T230041_20240120T230049_D00401_N_J_001.h5"
}
invoke_res = self.client.invoke(
FunctionName=self.function_name,
InvocationType="Event",
Payload=json.dumps(in_data),
)
logger.info("invoke_res: %s", invoke_res)
results = json.loads(invoke_res["Payload"].read().decode("utf-8"))
logger.info("results: %s", results)
assert results["errorType"] == "NoEvaluatorRegexMatched"
13 changes: 12 additions & 1 deletion tests/test_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from unity_initiator.actions import ACTION_MAP
from unity_initiator.evaluator import Evaluator
from unity_initiator.router import Router
from unity_initiator.router import NoEvaluatorRegexMatched, Router
from unity_initiator.utils.logger import logger

# mock default region
Expand Down Expand Up @@ -162,3 +162,14 @@ def test_execute_actions_for_nisar_ldf_url():
logger.info("results: %s", results)
for res in results:
assert res["success"]


@mock_aws
def test_unrecognized_url():
"""Test routing a url payload that is unrecognized: NISAR L0B example"""

url = "s3://bucket/prefix/NISAR_L0_PR_RRSD_063_136_A_129S_20240120T230041_20240120T230049_D00401_N_J_001.h5"
router_file = files("tests.resources").joinpath("test_router.yaml")
router = Router(router_file)
with pytest.raises(NoEvaluatorRegexMatched, match=r"No regex matched url"):
list(router.get_evaluators_by_url(url))

0 comments on commit eafec43

Please sign in to comment.