├── .github └── workflows │ ├── check-binaries.yml │ ├── integ-tests.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── THIRD-PARTY-LICENSES.md ├── cmd └── aws-lambda-rie │ ├── handlers.go │ ├── http.go │ ├── main.go │ ├── simple_bootstrap.go │ ├── simple_bootstrap_test.go │ └── util.go ├── go.mod ├── go.sum ├── lambda ├── agents │ ├── agent.go │ └── agent_test.go ├── appctx │ ├── appctx.go │ ├── appctxutil.go │ └── appctxutil_test.go ├── core │ ├── agent_state_names.go │ ├── agentsmap.go │ ├── agentsmap_test.go │ ├── agentutil.go │ ├── bandwidthlimiter │ │ ├── bandwidthlimiter.go │ │ ├── bandwidthlimiter_test.go │ │ ├── throttler.go │ │ ├── throttler_test.go │ │ ├── util.go │ │ └── util_test.go │ ├── credentials.go │ ├── credentials_test.go │ ├── directinvoke │ │ ├── customerheaders.go │ │ ├── customerheaders_test.go │ │ ├── directinvoke.go │ │ ├── directinvoke_test.go │ │ └── util.go │ ├── doc.go │ ├── externalagent.go │ ├── externalagent_states.go │ ├── externalagent_states_test.go │ ├── flow.go │ ├── gates.go │ ├── gates_test.go │ ├── internalagent.go │ ├── internalagent_states.go │ ├── internalagent_states_test.go │ ├── registrations.go │ ├── registrations_test.go │ ├── runtime_state_names.go │ ├── statejson │ │ └── description.go │ ├── states.go │ └── states_test.go ├── extensions │ └── extensions.go ├── fatalerror │ ├── fatalerror.go │ └── fatalerror_test.go ├── interop │ ├── bootstrap.go │ ├── cancellable_request.go │ ├── events_api.go │ ├── events_api_test.go │ ├── messages.go │ ├── model.go │ ├── model_test.go │ └── sandbox_model.go ├── logging │ ├── doc.go │ ├── internal_log.go │ └── internal_log_test.go ├── metering │ ├── time.go │ └── time_test.go ├── rapi │ ├── extensions_fuzz_test.go │ ├── handler │ │ ├── agentexiterror.go │ │ ├── agentiniterror.go │ │ ├── agentiniterror_test.go │ │ ├── agentnext.go │ │ ├── agentnext_test.go │ │ ├── agentregister.go │ │ ├── agentregister_test.go │ │ ├── constants.go │ │ ├── credentials.go │ │ ├── credentials_test.go │ │ ├── initerror.go │ │ ├── initerror_test.go │ │ ├── invocationerror.go │ │ ├── invocationerror_test.go │ │ ├── invocationnext.go │ │ ├── invocationnext_test.go │ │ ├── invocationresponse.go │ │ ├── invocationresponse_test.go │ │ ├── mime_type_error_cause_json.go │ │ ├── ping.go │ │ ├── restoreerror.go │ │ ├── restoreerror_test.go │ │ ├── restorenext.go │ │ ├── restorenext_test.go │ │ ├── runtimelogs.go │ │ ├── runtimelogs_stub.go │ │ ├── runtimelogs_stub_test.go │ │ └── runtimelogs_test.go │ ├── middleware │ │ ├── middleware.go │ │ └── middleware_test.go │ ├── model │ │ ├── agentevent.go │ │ ├── agentregisterresponse.go │ │ ├── cognitoidentity.go │ │ ├── error_cause.go │ │ ├── error_cause_compactor.go │ │ ├── error_cause_compactor_test.go │ │ ├── error_cause_test.go │ │ ├── errorresponse.go │ │ ├── statusresponse.go │ │ └── tracing.go │ ├── rapi_fuzz_test.go │ ├── rendering │ │ ├── doc.go │ │ ├── render_error.go │ │ ├── render_json.go │ │ └── rendering.go │ ├── router.go │ ├── router_test.go │ ├── security_test.go │ ├── server.go │ ├── server_test.go │ └── telemetry_logs_fuzz_test.go ├── rapid │ ├── exit.go │ ├── handlers.go │ ├── handlers_test.go │ ├── sandbox.go │ └── shutdown.go ├── rapidcore │ ├── env │ │ ├── constants.go │ │ ├── customer.go │ │ ├── environment.go │ │ ├── environment_test.go │ │ ├── rapidenv.go │ │ ├── util.go │ │ └── util_test.go │ ├── errors.go │ ├── runtime_release.go │ ├── runtime_release_test.go │ ├── sandbox_api.go │ ├── sandbox_builder.go │ ├── sandbox_emulator_api.go │ ├── server.go │ ├── server_test.go │ └── standalone │ │ ├── directInvokeHandler.go │ │ ├── eventLogHandler.go │ │ ├── executeHandler.go │ │ ├── initHandler.go │ │ ├── internalStateHandler.go │ │ ├── invokeHandler.go │ │ ├── middleware.go │ │ ├── pingHandler.go │ │ ├── reserveHandler.go │ │ ├── resetHandler.go │ │ ├── restoreHandler.go │ │ ├── router.go │ │ ├── shutdownHandler.go │ │ ├── telemetry │ │ ├── agent_writer.go │ │ ├── eventLog.go │ │ ├── events_api.go │ │ ├── logs_egress_api.go │ │ ├── structured_logger.go │ │ └── tracer.go │ │ ├── util.go │ │ ├── waitUntilInitializedHandler.go │ │ └── waitUntilReleaseHandler.go ├── supervisor │ ├── local_supervisor.go │ ├── local_supervisor_test.go │ └── model │ │ ├── model.go │ │ └── model_test.go ├── telemetry │ ├── constants.go │ ├── events_api.go │ ├── events_api_test.go │ ├── logs_egress_api.go │ ├── logs_subscription_api.go │ ├── tracer.go │ └── tracer_test.go └── testdata │ ├── agents │ └── bash_true.sh │ ├── async_assertion_utils.go │ ├── bash_function.sh │ ├── bash_runtime.sh │ ├── bash_script_with_child_proc.sh │ ├── env_setup_helpers.go │ ├── flowtesting.go │ ├── mockcommand.go │ ├── mockthread │ └── mockthread.go │ ├── mocktracer │ └── mocktracer.go │ └── parametrization.go └── test └── integration ├── local_lambda └── test_end_to_end.py └── testdata ├── Dockerfile-allinone └── main.py /.github/workflows/check-binaries.yml: -------------------------------------------------------------------------------- 1 | name: Check binaries 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 16 * * 1-5" # min h d Mo DoW / 9am PST M-F 7 | 8 | jobs: 9 | check-for-vulnerabilities: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | report_contents: ${{ steps.save-output.outputs.report_contents }} 13 | steps: 14 | - name: Setup python 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.11' 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | with: 21 | ref: main 22 | - name: Download latest release 23 | uses: robinraju/release-downloader@v1.10 24 | with: 25 | latest: true 26 | fileName: 'aws-lambda-rie*' 27 | out-file-path: "bin" 28 | - name: Run check for vulnerabilities 29 | id: check-binaries 30 | run: | 31 | make check-binaries 32 | - if: always() && failure() # `always()` to run even if the previous step failed. Failure means that there are vulnerabilities 33 | name: Save content of the vulnerabilities report as GitHub output 34 | id: save-output 35 | run: | 36 | report_csv="$(ls -tr output.cve-bin-*.csv 2>/dev/null | tail -n1)" # last file generated 37 | if [ -z "$report_csv" ]; then 38 | echo "No file with vulnerabilities. Probably a failure in previous step." 39 | else 40 | echo "Vulnerabilities stored in $report_csv" 41 | fi 42 | final_report="${report_csv}.txt" 43 | awk -F',' '{n=split($10, path, "/"); print $2,$3,$4,$5,path[n]}' "$report_csv" | column -t > "$final_report" # make the CSV nicer 44 | echo "report_contents<> "$GITHUB_OUTPUT" 45 | cat "$final_report" >> "$GITHUB_OUTPUT" 46 | echo "EOF" >> "$GITHUB_OUTPUT" 47 | - if: always() && steps.save-output.outputs.report_contents 48 | name: Build new binaries and check vulnerabilities again 49 | id: check-new-version 50 | run: | 51 | mkdir ./bin2 52 | mv ./bin/* ./bin2 53 | make compile-with-docker-all 54 | latest_version=$(strings bin/aws-lambda-rie* | grep '^go1\.' | sort | uniq) 55 | echo "latest_version=$latest_version" >> "$GITHUB_OUTPUT" 56 | make check-binaries 57 | - if: always() && steps.save-output.outputs.report_contents 58 | name: Save outputs for the check with the latest build 59 | id: save-new-version 60 | run: | 61 | if [ "${{ steps.check-new-version.outcome }}" == "failure" ]; then 62 | fixed="No" 63 | else 64 | fixed="Yes" 65 | fi 66 | echo "fixed=$fixed" >> "$GITHUB_OUTPUT" 67 | - if: always() && steps.save-output.outputs.report_contents 68 | name: Create GitHub Issue indicating vulnerabilities 69 | id: create-issue 70 | uses: dacbd/create-issue-action@main 71 | with: 72 | token: ${{ github.token }} 73 | title: | 74 | CVEs found in latest RIE release 75 | body: | 76 | ### CVEs found in latest RIE release 77 | ``` 78 | ${{ steps.save-output.outputs.report_contents }} 79 | ``` 80 | 81 | #### Are these resolved by building with the latest patch version of Go (${{ steps.check-new-version.outputs.latest_version }})?: 82 | > **${{ steps.save-new-version.outputs.fixed }}** 83 | -------------------------------------------------------------------------------- /.github/workflows/integ-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Integration Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - develop 7 | 8 | jobs: 9 | go-tests: 10 | runs-on: ubuntu-latest 11 | environment: 12 | name: integ-tests 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: run go tests 16 | run: make tests-with-docker 17 | integ-tests-x86: 18 | runs-on: ubuntu-latest 19 | environment: 20 | name: integ-tests 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: actions/setup-python@v5 24 | with: 25 | python-version: '3.11' 26 | - name: run integration tests 27 | run: make integ-tests-with-docker-x86-64 28 | integ-tests-arm64: 29 | runs-on: ubuntu-latest 30 | environment: 31 | name: integ-tests 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions/setup-python@v5 35 | with: 36 | python-version: '3.11' 37 | - name: run integration tests 38 | run: make integ-tests-with-docker-arm64 39 | integ-tests-old: 40 | runs-on: ubuntu-latest 41 | environment: 42 | name: integ-tests 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: actions/setup-python@v5 46 | with: 47 | python-version: '3.11' 48 | - name: run integration tests 49 | run: make integ-tests-with-docker-old -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseVersion: 7 | description: "Version to use for the release." 8 | required: true 9 | default: "X.Y" 10 | releaseBody: 11 | description: "Information about the release" 12 | required: true 13 | default: "New release" 14 | jobs: 15 | Release: 16 | environment: Release 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | ref: main 22 | - name: Set up python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: '3.11' 26 | - name: Build 27 | run: make compile-with-docker-all 28 | - name: Run Integ Tests 29 | run: | 30 | make tests-with-docker 31 | make integ-tests 32 | - name: Release 33 | uses: softprops/action-gh-release@v2 34 | with: 35 | name: Release ${{ github.event.inputs.releaseVersion }} 36 | tag_name: v${{ github.event.inputs.releaseVersion }} 37 | body: ${{ github.event.inputs.releaseBody }} 38 | files: | 39 | bin/aws-lambda-rie 40 | bin/aws-lambda-rie-arm64 41 | bin/aws-lambda-rie-x86_64 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /build 3 | /bin 4 | *.swp 5 | *.iml 6 | tags 7 | .idea 8 | .DS_Store 9 | .venv 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | This repository contains the source code and examples for the Runtime Interface Emulator. We will accept pull requests on documentation, examples, bug fixes and the Dockerfiles. We will also accept pull requests, issues and feedback on improvements to the Runtime Interface Emulator. However, our priority will be to maintain fidelity with AWS Lambda’s Runtime Interface on the cloud. 25 | 26 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 27 | 28 | 1. You are working against the latest source on the *main* branch. 29 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 30 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 31 | 32 | To send us a pull request, please: 33 | 34 | 1. Fork the repository. 35 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 36 | 3. Ensure local tests pass through `make integ-tests-and-compile` 37 | 4. Commit to your fork using clear commit messages. 38 | 5. Send us a pull request, answering any default questions in the pull request interface. 39 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 40 | 41 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 42 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 43 | 44 | 45 | ## Finding contributions to work on 46 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 47 | 48 | 49 | ## Code of Conduct 50 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 51 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 52 | opensource-codeofconduct@amazon.com with any additional questions or comments. 53 | 54 | 55 | ## Security issue notifications 56 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 57 | 58 | 59 | ## Licensing 60 | 61 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 62 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # RELEASE_BUILD_LINKER_FLAGS disables DWARF and symbol table generation to reduce binary size 2 | RELEASE_BUILD_LINKER_FLAGS=-s -w 3 | 4 | BINARY_NAME=aws-lambda-rie 5 | ARCH=x86_64 6 | GO_ARCH_old := amd64 7 | GO_ARCH_x86_64 := amd64 8 | GO_ARCH_arm64 := arm64 9 | DESTINATION_old:= bin/${BINARY_NAME} 10 | DESTINATION_x86_64 := bin/${BINARY_NAME}-x86_64 11 | DESTINATION_arm64 := bin/${BINARY_NAME}-arm64 12 | 13 | run_in_docker = docker run --env GOPROXY=direct -v $(shell pwd):/LambdaRuntimeLocal -w /LambdaRuntimeLocal golang:1.24 $(1) 14 | 15 | compile-with-docker-all: 16 | $(call run_in_docker, make compile-lambda-linux-all) 17 | 18 | compile-lambda-linux-all: 19 | make ARCH=x86_64 compile-lambda-linux 20 | make ARCH=arm64 compile-lambda-linux 21 | make ARCH=old compile-lambda-linux 22 | 23 | compile-with-docker: 24 | $(call run_in_docker, make ARCH=${ARCH} compile-lambda-linux) 25 | 26 | compile-lambda-linux: 27 | CGO_ENABLED=0 GOOS=linux GOARCH=${GO_ARCH_${ARCH}} go build -buildvcs=false -ldflags "${RELEASE_BUILD_LINKER_FLAGS}" -o ${DESTINATION_${ARCH}} ./cmd/aws-lambda-rie 28 | 29 | tests-with-docker: 30 | $(call run_in_docker, make tests) 31 | 32 | tests: 33 | go test ./... 34 | 35 | integ-tests-and-compile: tests 36 | make compile-lambda-linux-all 37 | make integ-tests 38 | 39 | integ-tests-with-docker: tests-with-docker 40 | make compile-with-docker-all 41 | make integ-tests 42 | 43 | prep-python: 44 | python3 -m venv .venv 45 | .venv/bin/pip install --upgrade pip 46 | .venv/bin/pip install requests parameterized 47 | 48 | exec-python-e2e-test: 49 | .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py 50 | 51 | integ-tests: 52 | make prep-python 53 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 54 | make TEST_ARCH=x86_64 TEST_PORT=8002 exec-python-e2e-test 55 | make TEST_ARCH=arm64 TEST_PORT=9002 exec-python-e2e-test 56 | make TEST_ARCH="" TEST_PORT=9052 exec-python-e2e-test 57 | 58 | integ-tests-with-docker-x86-64: 59 | make ARCH=x86_64 compile-with-docker 60 | make prep-python 61 | make TEST_ARCH=x86_64 TEST_PORT=8002 exec-python-e2e-test 62 | 63 | integ-tests-with-docker-arm64: 64 | make ARCH=arm64 compile-with-docker 65 | make prep-python 66 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 67 | make TEST_ARCH=arm64 TEST_PORT=9002 exec-python-e2e-test 68 | 69 | integ-tests-with-docker-old: 70 | make ARCH=old compile-with-docker 71 | make prep-python 72 | make TEST_ARCH="" TEST_PORT=9052 exec-python-e2e-test 73 | 74 | check-binaries: prep-python 75 | .venv/bin/pip install cve-bin-tool 76 | .venv/bin/python -m cve_bin_tool.cli bin/ -r go -d REDHAT,OSV,GAD,CURL --no-0-cve-report -f csv 77 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /cmd/aws-lambda-rie/http.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "net/http" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "go.amzn.com/lambda/interop" 11 | "go.amzn.com/lambda/rapidcore" 12 | ) 13 | 14 | func startHTTPServer(ipport string, sandbox *rapidcore.SandboxBuilder, bs interop.Bootstrap) { 15 | srv := &http.Server{ 16 | Addr: ipport, 17 | } 18 | 19 | // Pass a channel 20 | http.HandleFunc("/2015-03-31/functions/function/invocations", func(w http.ResponseWriter, r *http.Request) { 21 | InvokeHandler(w, r, sandbox.LambdaInvokeAPI(), bs) 22 | }) 23 | 24 | // go routine (main thread waits) 25 | if err := srv.ListenAndServe(); err != nil { 26 | log.Panic(err) 27 | } 28 | 29 | log.Warnf("Listening on %s", ipport) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/aws-lambda-rie/simple_bootstrap.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | 11 | "go.amzn.com/lambda/fatalerror" 12 | "go.amzn.com/lambda/interop" 13 | "go.amzn.com/lambda/rapidcore/env" 14 | ) 15 | 16 | // the type implement a simpler version of the Bootstrap 17 | // this is useful in the Standalone Core implementation. 18 | type simpleBootstrap struct { 19 | cmd []string 20 | workingDir string 21 | } 22 | 23 | func NewSimpleBootstrap(cmd []string, currentWorkingDir string) interop.Bootstrap { 24 | if currentWorkingDir == "" { 25 | // use the root directory as the default working directory 26 | currentWorkingDir = "/" 27 | } 28 | 29 | // a single candidate command makes it automatically valid 30 | return &simpleBootstrap{ 31 | cmd: cmd, 32 | workingDir: currentWorkingDir, 33 | } 34 | } 35 | 36 | func (b *simpleBootstrap) Cmd() ([]string, error) { 37 | return b.cmd, nil 38 | } 39 | 40 | // Cwd returns the working directory of the bootstrap process 41 | // The path is validated against the chroot identified by `root` 42 | func (b *simpleBootstrap) Cwd() (string, error) { 43 | if !filepath.IsAbs(b.workingDir) { 44 | return "", fmt.Errorf("the working directory '%s' is invalid, it needs to be an absolute path", b.workingDir) 45 | } 46 | 47 | // evaluate the path relatively to the domain's mnt namespace root 48 | if _, err := os.Stat(b.workingDir); os.IsNotExist(err) { 49 | return "", fmt.Errorf("the working directory doesn't exist: %s", b.workingDir) 50 | } 51 | 52 | return b.workingDir, nil 53 | } 54 | 55 | // Env returns the environment variables available to 56 | // the bootstrap process 57 | func (b *simpleBootstrap) Env(e *env.Environment) map[string]string { 58 | return e.RuntimeExecEnv() 59 | } 60 | 61 | // ExtraFiles returns the extra file descriptors apart from 1 & 2 to be passed to runtime 62 | func (b *simpleBootstrap) ExtraFiles() []*os.File { 63 | return make([]*os.File, 0) 64 | } 65 | 66 | func (b *simpleBootstrap) CachedFatalError(err error) (fatalerror.ErrorType, string, bool) { 67 | // not implemented as it is not needed in Core but we need to fullfil the interface anyway 68 | return fatalerror.ErrorType(""), "", false 69 | } 70 | -------------------------------------------------------------------------------- /cmd/aws-lambda-rie/simple_bootstrap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | "reflect" 9 | "testing" 10 | 11 | "go.amzn.com/lambda/rapidcore/env" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestSimpleBootstrap(t *testing.T) { 17 | tmpFile, err := os.CreateTemp("", "oci-test-bootstrap") 18 | assert.NoError(t, err) 19 | defer os.Remove(tmpFile.Name()) 20 | 21 | // Setup single cmd candidate 22 | file := []string{tmpFile.Name(), "--arg1 s", "foo"} 23 | cmdCandidate := file 24 | 25 | // Setup working dir 26 | cwd, err := os.Getwd() 27 | assert.NoError(t, err) 28 | 29 | // Setup environment 30 | environment := env.NewEnvironment() 31 | environment.StoreRuntimeAPIEnvironmentVariable("host:port") 32 | environment.StoreEnvironmentVariablesFromInit(map[string]string{}, "", "", "", "", "", "") 33 | 34 | // Test 35 | b := NewSimpleBootstrap(cmdCandidate, cwd) 36 | bCwd, err := b.Cwd() 37 | assert.NoError(t, err) 38 | assert.Equal(t, cwd, bCwd) 39 | assert.True(t, reflect.DeepEqual(environment.RuntimeExecEnv(), b.Env(environment))) 40 | 41 | cmd, err := b.Cmd() 42 | assert.NoError(t, err) 43 | assert.Equal(t, file, cmd) 44 | } 45 | 46 | func TestSimpleBootstrapCmdNonExistingCandidate(t *testing.T) { 47 | // Setup inexistent single cmd candidate 48 | file := []string{"/foo/bar", "--arg1 s", "foo"} 49 | cmdCandidate := file 50 | 51 | // Setup working dir 52 | cwd, err := os.Getwd() 53 | assert.NoError(t, err) 54 | 55 | // Setup environment 56 | environment := env.NewEnvironment() 57 | environment.StoreRuntimeAPIEnvironmentVariable("host:port") 58 | environment.StoreEnvironmentVariablesFromInit(map[string]string{}, "", "", "", "", "", "") 59 | 60 | // Test 61 | b := NewSimpleBootstrap(cmdCandidate, cwd) 62 | bCwd, err := b.Cwd() 63 | assert.NoError(t, err) 64 | assert.Equal(t, cwd, bCwd) 65 | assert.True(t, reflect.DeepEqual(environment.RuntimeExecEnv(), b.Env(environment))) 66 | 67 | // No validations run against single candidates 68 | cmd, err := b.Cmd() 69 | assert.NoError(t, err) 70 | assert.Equal(t, file, cmd) 71 | } 72 | 73 | func TestSimpleBootstrapCmdDefaultWorkingDir(t *testing.T) { 74 | b := NewSimpleBootstrap([]string{}, "") 75 | bCwd, err := b.Cwd() 76 | assert.NoError(t, err) 77 | assert.Equal(t, "/", bCwd) 78 | } 79 | -------------------------------------------------------------------------------- /cmd/aws-lambda-rie/util.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "net/http") 9 | 10 | type ErrorType int 11 | 12 | const ( 13 | ClientInvalidRequest ErrorType = iota 14 | ) 15 | 16 | func (t ErrorType) String() string { 17 | switch t { 18 | case ClientInvalidRequest: 19 | return "Client.InvalidRequest" 20 | } 21 | return fmt.Sprintf("Cannot stringify standalone.ErrorType.%d", int(t)) 22 | } 23 | 24 | type ResponseWriterProxy struct { 25 | Body []byte 26 | StatusCode int 27 | } 28 | 29 | func (w *ResponseWriterProxy) Header() http.Header { 30 | return http.Header{} 31 | } 32 | 33 | func (w *ResponseWriterProxy) Write(b []byte) (int, error) { 34 | w.Body = b 35 | return 0, nil 36 | } 37 | 38 | func (w *ResponseWriterProxy) WriteHeader(statusCode int) { 39 | w.StatusCode = statusCode 40 | } 41 | 42 | func (w *ResponseWriterProxy) IsError() bool { 43 | return w.StatusCode != 0 && w.StatusCode/100 != 2 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go.amzn.com 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/aws/aws-lambda-go v1.46.0 7 | github.com/go-chi/chi v1.5.5 8 | github.com/google/uuid v1.6.0 9 | github.com/jessevdk/go-flags v1.5.0 10 | github.com/sirupsen/logrus v1.9.3 11 | github.com/stretchr/testify v1.9.0 12 | golang.org/x/sync v0.6.0 13 | ) 14 | 15 | require ( 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | github.com/stretchr/objx v0.5.2 // indirect 19 | golang.org/x/sys v0.14.0 // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-lambda-go v1.46.0 h1:UWVnvh2h2gecOlFhHQfIPQcD8pL/f7pVCutmFl+oXU8= 2 | github.com/aws/aws-lambda-go v1.46.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= 7 | github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= 8 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 9 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 10 | github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= 11 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 15 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 18 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 19 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 20 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 21 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 22 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= 23 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 24 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 25 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= 27 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 30 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 31 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 32 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 33 | -------------------------------------------------------------------------------- /lambda/agents/agent.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package agents 5 | 6 | import ( 7 | "os" 8 | "path" 9 | "path/filepath" 10 | 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // ListExternalAgentPaths return a list of external agents found in a given directory 15 | func ListExternalAgentPaths(dir string, root string) []string { 16 | var agentPaths []string 17 | if !isCanonical(dir) || !isCanonical(root) { 18 | log.Warningf("Agents base paths are not absolute and in canonical form: %s, %s", dir, root) 19 | return agentPaths 20 | } 21 | fullDir := path.Join(root, dir) 22 | files, err := os.ReadDir(fullDir) 23 | 24 | if err != nil { 25 | if os.IsNotExist(err) { 26 | log.Infof("The extension's directory %q does not exist, assuming no extensions to be loaded.", fullDir) 27 | } else { 28 | // TODO - Should this return an error rather than ignore failing to load? 29 | log.WithError(err).Error("Cannot list external agents") 30 | } 31 | 32 | return agentPaths 33 | } 34 | 35 | for _, file := range files { 36 | if !file.IsDir() { 37 | // The returned path is absolute wrt to `root`. This allows 38 | // to exec the agents in their own mount namespace 39 | p := path.Join("/", dir, file.Name()) 40 | agentPaths = append(agentPaths, p) 41 | } 42 | } 43 | return agentPaths 44 | } 45 | 46 | func isCanonical(path string) bool { 47 | absPath, err := filepath.Abs(path) 48 | return err == nil && absPath == path 49 | } 50 | -------------------------------------------------------------------------------- /lambda/appctx/appctx.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package appctx 5 | 6 | import ( 7 | "sync" 8 | ) 9 | 10 | // A Key type is used as a key for storing values in the application context. 11 | type Key int 12 | 13 | type InitType int 14 | 15 | const ( 16 | // AppCtxInvokeErrorTraceDataKey is used for storing deferred invoke error cause header value. 17 | // Only used by xray. TODO refactor xray interface so it doesn't use appctx 18 | AppCtxInvokeErrorTraceDataKey Key = iota 19 | 20 | // AppCtxRuntimeReleaseKey is used for storing runtime release information (parsed from User_Agent Http header string). 21 | AppCtxRuntimeReleaseKey 22 | 23 | // AppCtxInteropServerKey is used to store a reference to the interop server. 24 | AppCtxInteropServerKey 25 | 26 | // AppCtxResponseSenderKey is used to store a reference to the response sender 27 | AppCtxResponseSenderKey 28 | 29 | // AppCtxFirstFatalErrorKey is used to store first unrecoverable error message encountered to propagate it to slicer with DONE(errortype) or DONEFAIL(errortype) 30 | AppCtxFirstFatalErrorKey 31 | 32 | // AppCtxInitType is used to store the init type (init caching or plain INIT) 33 | AppCtxInitType 34 | 35 | // AppCtxSandbox type is used to store the sandbox type (SandboxClassic or SandboxPreWarmed) 36 | AppCtxSandboxType 37 | ) 38 | 39 | // Possible values for AppCtxInitType key 40 | const ( 41 | Init InitType = iota 42 | InitCaching 43 | ) 44 | 45 | // ApplicationContext is an application scope context. 46 | type ApplicationContext interface { 47 | Store(key Key, value interface{}) 48 | Load(key Key) (value interface{}, ok bool) 49 | Delete(key Key) 50 | GetOrDefault(key Key, defaultValue interface{}) interface{} 51 | StoreIfNotExists(key Key, value interface{}) interface{} 52 | } 53 | 54 | type applicationContext struct { 55 | mux *sync.Mutex 56 | m map[Key]interface{} 57 | } 58 | 59 | func (appCtx *applicationContext) Store(key Key, value interface{}) { 60 | appCtx.mux.Lock() 61 | defer appCtx.mux.Unlock() 62 | appCtx.m[key] = value 63 | } 64 | 65 | func (appCtx *applicationContext) StoreIfNotExists(key Key, value interface{}) interface{} { 66 | appCtx.mux.Lock() 67 | defer appCtx.mux.Unlock() 68 | existing, found := appCtx.m[key] 69 | if found { 70 | return existing 71 | } 72 | appCtx.m[key] = value 73 | return nil 74 | } 75 | 76 | func (appCtx *applicationContext) Load(key Key) (value interface{}, ok bool) { 77 | appCtx.mux.Lock() 78 | defer appCtx.mux.Unlock() 79 | value, ok = appCtx.m[key] 80 | return 81 | } 82 | 83 | func (appCtx *applicationContext) Delete(key Key) { 84 | appCtx.mux.Lock() 85 | defer appCtx.mux.Unlock() 86 | delete(appCtx.m, key) 87 | } 88 | 89 | func (appCtx *applicationContext) GetOrDefault(key Key, defaultValue interface{}) interface{} { 90 | if value, ok := appCtx.Load(key); ok { 91 | return value 92 | } 93 | return defaultValue 94 | } 95 | 96 | // NewApplicationContext returns a new instance of application context. 97 | func NewApplicationContext() ApplicationContext { 98 | return &applicationContext{ 99 | mux: &sync.Mutex{}, 100 | m: make(map[Key]interface{}), 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lambda/core/agent_state_names.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | // String values of possibles agent states 7 | const ( 8 | AgentStartedStateName = "Started" 9 | AgentRegisteredStateName = "Registered" 10 | AgentReadyStateName = "Ready" 11 | AgentRunningStateName = "Running" 12 | AgentInitErrorStateName = "InitError" 13 | AgentExitErrorStateName = "ExitError" 14 | AgentShutdownFailedStateName = "ShutdownFailed" 15 | AgentExitedStateName = "Exited" 16 | AgentLaunchErrorName = "LaunchError" 17 | ) 18 | -------------------------------------------------------------------------------- /lambda/core/agentsmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | import ( 7 | "github.com/google/uuid" 8 | "github.com/stretchr/testify/require" 9 | "testing" 10 | ) 11 | 12 | func TestExternalAgentsMapLookupByName(t *testing.T) { 13 | m := NewExternalAgentsMap() 14 | 15 | err := m.Insert(&ExternalAgent{Name: "a", ID: uuid.New()}) 16 | require.NoError(t, err) 17 | agentIn := &ExternalAgent{Name: "b", ID: uuid.New()} 18 | err = m.Insert(agentIn) 19 | require.NoError(t, err) 20 | err = m.Insert(&ExternalAgent{Name: "c", ID: uuid.New()}) 21 | require.NoError(t, err) 22 | 23 | agentOut, found := m.FindByName(agentIn.Name) 24 | require.True(t, found) 25 | require.Equal(t, agentIn, agentOut) 26 | } 27 | 28 | func TestExternalAgentsMapLookupByID(t *testing.T) { 29 | m := NewExternalAgentsMap() 30 | 31 | err := m.Insert(&ExternalAgent{Name: "a", ID: uuid.New()}) 32 | require.NoError(t, err) 33 | agentIn := &ExternalAgent{Name: "b", ID: uuid.New()} 34 | err = m.Insert(agentIn) 35 | require.NoError(t, err) 36 | err = m.Insert(&ExternalAgent{Name: "c", ID: uuid.New()}) 37 | require.NoError(t, err) 38 | 39 | agentOut, found := m.FindByID(agentIn.ID) 40 | require.True(t, found) 41 | require.Equal(t, agentIn, agentOut) 42 | } 43 | 44 | func TestExternalAgentsMapInsertNameCollision(t *testing.T) { 45 | m := NewExternalAgentsMap() 46 | 47 | err := m.Insert(&ExternalAgent{Name: "a", ID: uuid.New()}) 48 | require.NoError(t, err) 49 | 50 | err = m.Insert(&ExternalAgent{Name: "a", ID: uuid.New()}) 51 | require.Equal(t, err, ErrAgentNameCollision) 52 | } 53 | 54 | func TestExternalAgentsMapInsertIDCollision(t *testing.T) { 55 | m := NewExternalAgentsMap() 56 | 57 | id := uuid.New() 58 | 59 | err := m.Insert(&ExternalAgent{Name: "a", ID: id}) 60 | require.NoError(t, err) 61 | 62 | err = m.Insert(&ExternalAgent{Name: "b", ID: id}) 63 | require.Equal(t, err, ErrAgentIDCollision) 64 | } 65 | 66 | func TestInternalAgentsMapLookupByName(t *testing.T) { 67 | m := NewInternalAgentsMap() 68 | 69 | err := m.Insert(&InternalAgent{Name: "a", ID: uuid.New()}) 70 | require.NoError(t, err) 71 | agentIn := &InternalAgent{Name: "b", ID: uuid.New()} 72 | err = m.Insert(agentIn) 73 | require.NoError(t, err) 74 | err = m.Insert(&InternalAgent{Name: "c", ID: uuid.New()}) 75 | require.NoError(t, err) 76 | 77 | agentOut, found := m.FindByName(agentIn.Name) 78 | require.True(t, found) 79 | require.Equal(t, agentIn, agentOut) 80 | } 81 | 82 | func TestInternalAgentsMapLookupByID(t *testing.T) { 83 | m := NewInternalAgentsMap() 84 | 85 | err := m.Insert(&InternalAgent{Name: "a", ID: uuid.New()}) 86 | require.NoError(t, err) 87 | agentIn := &InternalAgent{Name: "b", ID: uuid.New()} 88 | err = m.Insert(agentIn) 89 | require.NoError(t, err) 90 | err = m.Insert(&InternalAgent{Name: "c", ID: uuid.New()}) 91 | require.NoError(t, err) 92 | 93 | agentOut, found := m.FindByID(agentIn.ID) 94 | require.True(t, found) 95 | require.Equal(t, agentIn, agentOut) 96 | } 97 | 98 | func TestInternalAgentsMapInsertNameCollision(t *testing.T) { 99 | m := NewInternalAgentsMap() 100 | 101 | err := m.Insert(&InternalAgent{Name: "a", ID: uuid.New()}) 102 | require.NoError(t, err) 103 | 104 | err = m.Insert(&InternalAgent{Name: "a", ID: uuid.New()}) 105 | require.Equal(t, err, ErrAgentNameCollision) 106 | } 107 | 108 | func TestInternalAgentsMapInsertIDCollision(t *testing.T) { 109 | m := NewInternalAgentsMap() 110 | 111 | id := uuid.New() 112 | 113 | err := m.Insert(&InternalAgent{Name: "a", ID: id}) 114 | require.NoError(t, err) 115 | 116 | err = m.Insert(&InternalAgent{Name: "b", ID: id}) 117 | require.Equal(t, err, ErrAgentIDCollision) 118 | } 119 | -------------------------------------------------------------------------------- /lambda/core/agentutil.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | import ( 7 | "errors" 8 | ) 9 | 10 | var errInvalidEventType = errors.New("ErrorInvalidEventType") 11 | var errEventNotSupportedForInternalAgent = errors.New("ShutdownEventNotSupportedForInternalExtension") 12 | 13 | type disallowEverything struct { 14 | } 15 | 16 | // Register 17 | func (s *disallowEverything) Register(events []Event) error { return ErrNotAllowed } 18 | 19 | // Ready 20 | func (s *disallowEverything) Ready() error { return ErrNotAllowed } 21 | 22 | // InitError 23 | func (s *disallowEverything) InitError(errorType string) error { return ErrNotAllowed } 24 | 25 | // ExitError 26 | func (s *disallowEverything) ExitError(errorType string) error { return ErrNotAllowed } 27 | 28 | // ShutdownFailed 29 | func (s *disallowEverything) ShutdownFailed() error { return ErrNotAllowed } 30 | 31 | // Exited 32 | func (s *disallowEverything) Exited() error { return ErrNotAllowed } 33 | 34 | // LaunchError 35 | func (s *disallowEverything) LaunchError(error) error { return ErrNotAllowed } 36 | -------------------------------------------------------------------------------- /lambda/core/bandwidthlimiter/bandwidthlimiter.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package bandwidthlimiter 5 | 6 | import ( 7 | "io" 8 | 9 | "go.amzn.com/lambda/interop" 10 | ) 11 | 12 | func BandwidthLimitingCopy(dst *BandwidthLimitingWriter, src io.Reader) (written int64, err error) { 13 | written, err = io.Copy(dst, src) 14 | _ = dst.Close() 15 | return 16 | } 17 | 18 | func NewBandwidthLimitingWriter(w io.Writer, bucket *Bucket) (*BandwidthLimitingWriter, error) { 19 | throttler, err := NewThrottler(bucket) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &BandwidthLimitingWriter{w: w, th: throttler}, nil 24 | } 25 | 26 | type BandwidthLimitingWriter struct { 27 | w io.Writer 28 | th *Throttler 29 | } 30 | 31 | func (w *BandwidthLimitingWriter) ChunkedWrite(p []byte) (n int, err error) { 32 | i := NewChunkIterator(p, int(w.th.b.capacity)) 33 | for { 34 | buf := i.Next() 35 | if buf == nil { 36 | return 37 | } 38 | written, writeErr := w.th.bandwidthLimitingWrite(w.w, buf) 39 | n += written 40 | if writeErr != nil { 41 | return n, writeErr 42 | } 43 | } 44 | } 45 | 46 | func (w *BandwidthLimitingWriter) Write(p []byte) (n int, err error) { 47 | w.th.start() 48 | if int64(len(p)) > w.th.b.capacity { 49 | return w.ChunkedWrite(p) 50 | } 51 | return w.th.bandwidthLimitingWrite(w.w, p) 52 | } 53 | 54 | func (w *BandwidthLimitingWriter) Close() (err error) { 55 | w.th.stop() 56 | return 57 | } 58 | 59 | func (w *BandwidthLimitingWriter) GetMetrics() (metrics *interop.InvokeResponseMetrics) { 60 | return w.th.metrics 61 | } 62 | -------------------------------------------------------------------------------- /lambda/core/bandwidthlimiter/bandwidthlimiter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package bandwidthlimiter 5 | 6 | import ( 7 | "bytes" 8 | "io" 9 | "strings" 10 | "testing" 11 | "time" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestBandwidthLimitingCopy(t *testing.T) { 17 | var size10mb int64 = 10 * 1024 * 1024 18 | 19 | inputBuffer := []byte(strings.Repeat("a", int(size10mb))) 20 | reader := bytes.NewReader(inputBuffer) 21 | 22 | bucket, err := NewBucket(size10mb/2, size10mb/4, size10mb/2, time.Millisecond/2) 23 | assert.NoError(t, err) 24 | 25 | internalWriter := bytes.NewBuffer(make([]byte, 0, size10mb)) 26 | writer, err := NewBandwidthLimitingWriter(internalWriter, bucket) 27 | assert.NoError(t, err) 28 | 29 | n, err := BandwidthLimitingCopy(writer, reader) 30 | assert.Equal(t, size10mb, n) 31 | assert.Equal(t, nil, err) 32 | assert.Equal(t, inputBuffer, internalWriter.Bytes()) 33 | } 34 | 35 | type ErrorBufferWriter struct { 36 | w ByteBufferWriter 37 | failAfter int 38 | } 39 | 40 | func (w *ErrorBufferWriter) Write(p []byte) (n int, err error) { 41 | if w.failAfter >= 1 { 42 | w.failAfter-- 43 | } 44 | n, err = w.w.Write(p) 45 | if w.failAfter == 0 { 46 | return n, io.ErrUnexpectedEOF 47 | } 48 | return n, err 49 | } 50 | 51 | func (w *ErrorBufferWriter) Bytes() []byte { 52 | return w.w.Bytes() 53 | } 54 | 55 | func TestNewBandwidthLimitingWriter(t *testing.T) { 56 | type testCase struct { 57 | refillNumber int64 58 | internalWriter ByteBufferWriter 59 | inputBuffer []byte 60 | expectedN int 61 | expectedError error 62 | } 63 | testCases := []testCase{ 64 | { 65 | refillNumber: 2, 66 | internalWriter: bytes.NewBuffer(make([]byte, 0, 36)), // buffer size greater than bucket size 67 | inputBuffer: []byte(strings.Repeat("a", 36)), 68 | expectedN: 36, 69 | expectedError: nil, 70 | }, 71 | { 72 | refillNumber: 2, 73 | internalWriter: bytes.NewBuffer(make([]byte, 0, 12)), // buffer size lesser than bucket size 74 | inputBuffer: []byte(strings.Repeat("a", 12)), 75 | expectedN: 12, 76 | expectedError: nil, 77 | }, 78 | { 79 | // buffer size greater than bucket size and error after two Write() invocations 80 | refillNumber: 2, 81 | internalWriter: &ErrorBufferWriter{w: bytes.NewBuffer(make([]byte, 0, 36)), failAfter: 2}, 82 | inputBuffer: []byte(strings.Repeat("a", 36)), 83 | expectedN: 32, 84 | expectedError: io.ErrUnexpectedEOF, 85 | }, 86 | } 87 | 88 | for _, test := range testCases { 89 | bucket, err := NewBucket(16, 8, test.refillNumber, 100*time.Millisecond) 90 | assert.NoError(t, err) 91 | 92 | writer, err := NewBandwidthLimitingWriter(test.internalWriter, bucket) 93 | assert.NoError(t, err) 94 | assert.False(t, writer.th.running) 95 | 96 | n, err := writer.Write(test.inputBuffer) 97 | assert.True(t, writer.th.running) 98 | assert.Equal(t, test.expectedN, n) 99 | assert.Equal(t, test.expectedError, err) 100 | assert.Equal(t, test.inputBuffer[:n], test.internalWriter.Bytes()) 101 | 102 | err = writer.Close() 103 | assert.Nil(t, err) 104 | assert.False(t, writer.th.running) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lambda/core/bandwidthlimiter/throttler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package bandwidthlimiter 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "io" 10 | "sync" 11 | "time" 12 | 13 | log "github.com/sirupsen/logrus" 14 | 15 | "go.amzn.com/lambda/interop" 16 | "go.amzn.com/lambda/metering" 17 | ) 18 | 19 | var ErrBufferSizeTooLarge = errors.New("buffer size cannot be greater than bucket size") 20 | 21 | func NewBucket(capacity int64, initialTokenCount int64, refillNumber int64, refillInterval time.Duration) (*Bucket, error) { 22 | if capacity <= 0 || initialTokenCount < 0 || refillNumber <= 0 || refillInterval <= 0 || 23 | capacity < initialTokenCount { 24 | errorMsg := fmt.Sprintf("invalid bucket parameters (capacity: %d, initialTokenCount: %d, refillNumber: %d,"+ 25 | "refillInterval: %d)", capacity, initialTokenCount, refillInterval, refillInterval) 26 | log.Error(errorMsg) 27 | return nil, errors.New(errorMsg) 28 | } 29 | return &Bucket{ 30 | capacity: capacity, 31 | tokenCount: initialTokenCount, 32 | refillNumber: refillNumber, 33 | refillInterval: refillInterval, 34 | mutex: sync.Mutex{}, 35 | }, nil 36 | } 37 | 38 | type Bucket struct { 39 | capacity int64 40 | tokenCount int64 41 | refillNumber int64 42 | refillInterval time.Duration 43 | mutex sync.Mutex 44 | } 45 | 46 | func (b *Bucket) produceTokens() { 47 | b.mutex.Lock() 48 | defer b.mutex.Unlock() 49 | if b.tokenCount < b.capacity { 50 | b.tokenCount = min64(b.tokenCount+b.refillNumber, b.capacity) 51 | } 52 | } 53 | 54 | func (b *Bucket) consumeTokens(n int64) bool { 55 | b.mutex.Lock() 56 | defer b.mutex.Unlock() 57 | if n <= b.tokenCount { 58 | b.tokenCount -= n 59 | return true 60 | } 61 | return false 62 | } 63 | 64 | func (b *Bucket) getTokenCount() int64 { 65 | b.mutex.Lock() 66 | defer b.mutex.Unlock() 67 | return b.tokenCount 68 | } 69 | 70 | func NewThrottler(bucket *Bucket) (*Throttler, error) { 71 | if bucket == nil { 72 | errorMsg := "cannot create a throttler with nil bucket" 73 | log.Error(errorMsg) 74 | return nil, errors.New(errorMsg) 75 | } 76 | return &Throttler{ 77 | b: bucket, 78 | running: false, 79 | produced: make(chan int64), 80 | done: make(chan struct{}), 81 | // FIXME: 82 | // The runtime tells whether the function response mode is streaming or not. 83 | // Ideally, we would want to use that value here. Since I'm just rebasing, I will leave 84 | // as-is, but we should use that instead of relying on our memory to set this here 85 | // because we "know" it's a streaming code path. 86 | metrics: &interop.InvokeResponseMetrics{FunctionResponseMode: interop.FunctionResponseModeStreaming}, 87 | }, nil 88 | } 89 | 90 | type Throttler struct { 91 | b *Bucket 92 | running bool 93 | produced chan int64 94 | done chan struct{} 95 | metrics *interop.InvokeResponseMetrics 96 | } 97 | 98 | func (th *Throttler) start() { 99 | if th.running { 100 | return 101 | } 102 | th.running = true 103 | th.metrics.StartReadingResponseMonoTimeMs = metering.Monotime() 104 | go func() { 105 | ticker := time.NewTicker(th.b.refillInterval) 106 | for { 107 | select { 108 | case <-ticker.C: 109 | th.b.produceTokens() 110 | select { 111 | case th.produced <- metering.Monotime(): 112 | default: 113 | } 114 | case <-th.done: 115 | ticker.Stop() 116 | return 117 | } 118 | } 119 | }() 120 | } 121 | 122 | func (th *Throttler) stop() { 123 | if !th.running { 124 | return 125 | } 126 | th.running = false 127 | th.metrics.FinishReadingResponseMonoTimeMs = metering.Monotime() 128 | durationMs := (th.metrics.FinishReadingResponseMonoTimeMs - th.metrics.StartReadingResponseMonoTimeMs) / int64(time.Millisecond) 129 | if durationMs > 0 { 130 | th.metrics.OutboundThroughputBps = (th.metrics.ProducedBytes / durationMs) * int64(time.Second/time.Millisecond) 131 | } else { 132 | th.metrics.OutboundThroughputBps = -1 133 | } 134 | th.done <- struct{}{} 135 | } 136 | 137 | func (th *Throttler) bandwidthLimitingWrite(w io.Writer, p []byte) (written int, err error) { 138 | n := int64(len(p)) 139 | if n > th.b.capacity { 140 | return 0, ErrBufferSizeTooLarge 141 | } 142 | for { 143 | if th.b.consumeTokens(n) { 144 | written, err = w.Write(p) 145 | th.metrics.ProducedBytes += int64(written) 146 | return 147 | } 148 | waitStart := metering.Monotime() 149 | elapsed := <-th.produced - waitStart 150 | if elapsed > 0 { 151 | th.metrics.TimeShapedNs += elapsed 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /lambda/core/bandwidthlimiter/util.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package bandwidthlimiter 5 | 6 | func min(a, b int) int { 7 | if a < b { 8 | return a 9 | } 10 | return b 11 | } 12 | 13 | func min64(a, b int64) int64 { 14 | if a < b { 15 | return a 16 | } 17 | return b 18 | } 19 | 20 | func NewChunkIterator(buf []byte, chunkSize int) *ChunkIterator { 21 | if buf == nil { 22 | return nil 23 | } 24 | return &ChunkIterator{ 25 | buf: buf, 26 | chunkSize: chunkSize, 27 | offset: 0, 28 | } 29 | } 30 | 31 | type ChunkIterator struct { 32 | buf []byte 33 | chunkSize int 34 | offset int 35 | } 36 | 37 | func (i *ChunkIterator) Next() []byte { 38 | begin := i.offset 39 | end := min(i.offset+i.chunkSize, len(i.buf)) 40 | i.offset = end 41 | 42 | if begin == end { 43 | return nil 44 | } 45 | return i.buf[begin:end] 46 | } 47 | -------------------------------------------------------------------------------- /lambda/core/bandwidthlimiter/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package bandwidthlimiter 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestNewChunkIterator(t *testing.T) { 13 | buf := []byte("abcdefghijk") 14 | 15 | type testCase struct { 16 | buf []byte 17 | chunkSize int 18 | expectedResult [][]byte 19 | } 20 | testCases := []testCase{ 21 | {buf: nil, chunkSize: 0, expectedResult: [][]byte{}}, 22 | {buf: nil, chunkSize: 1, expectedResult: [][]byte{}}, 23 | {buf: buf, chunkSize: 0, expectedResult: [][]byte{}}, 24 | {buf: buf, chunkSize: 1, expectedResult: [][]byte{ 25 | []byte("a"), []byte("b"), []byte("c"), []byte("d"), []byte("e"), []byte("f"), []byte("g"), []byte("h"), 26 | []byte("i"), []byte("j"), []byte("k"), 27 | }}, 28 | {buf: buf, chunkSize: 4, expectedResult: [][]byte{[]byte("abcd"), []byte("efgh"), []byte("ijk")}}, 29 | {buf: buf, chunkSize: 5, expectedResult: [][]byte{[]byte("abcde"), []byte("fghij"), []byte("k")}}, 30 | {buf: buf, chunkSize: 11, expectedResult: [][]byte{[]byte("abcdefghijk")}}, 31 | {buf: buf, chunkSize: 12, expectedResult: [][]byte{[]byte("abcdefghijk")}}, 32 | } 33 | 34 | for _, test := range testCases { 35 | iterator := NewChunkIterator(test.buf, test.chunkSize) 36 | if test.buf == nil { 37 | assert.Nil(t, iterator) 38 | } else { 39 | for _, expectedChunk := range test.expectedResult { 40 | assert.Equal(t, expectedChunk, iterator.Next()) 41 | } 42 | assert.Nil(t, iterator.Next()) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lambda/core/credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | import ( 7 | "fmt" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | const ( 13 | UNBLOCKED = iota 14 | BLOCKED 15 | ) 16 | 17 | var ErrCredentialsNotFound = fmt.Errorf("credentials not found for the provided token") 18 | 19 | type Credentials struct { 20 | AwsKey string `json:"AccessKeyId"` 21 | AwsSecret string `json:"SecretAccessKey"` 22 | AwsSession string `json:"Token"` 23 | Expiration time.Time `json:"Expiration"` 24 | } 25 | 26 | type CredentialsService interface { 27 | SetCredentials(token, awsKey, awsSecret, awsSession string, expiration time.Time) 28 | GetCredentials(token string) (*Credentials, error) 29 | UpdateCredentials(awsKey, awsSecret, awsSession string, expiration time.Time) error 30 | } 31 | 32 | type credentialsServiceImpl struct { 33 | credentials map[string]Credentials 34 | contentMutex *sync.Mutex 35 | serviceMutex *sync.Mutex 36 | currentState int 37 | } 38 | 39 | func NewCredentialsService() CredentialsService { 40 | credentialsService := &credentialsServiceImpl{ 41 | credentials: make(map[string]Credentials), 42 | contentMutex: &sync.Mutex{}, 43 | serviceMutex: &sync.Mutex{}, 44 | currentState: UNBLOCKED, 45 | } 46 | 47 | return credentialsService 48 | } 49 | 50 | func (c *credentialsServiceImpl) SetCredentials(token, awsKey, awsSecret, awsSession string, expiration time.Time) { 51 | c.contentMutex.Lock() 52 | defer c.contentMutex.Unlock() 53 | 54 | c.credentials[token] = Credentials{ 55 | AwsKey: awsKey, 56 | AwsSecret: awsSecret, 57 | AwsSession: awsSession, 58 | Expiration: expiration, 59 | } 60 | } 61 | 62 | func (c *credentialsServiceImpl) GetCredentials(token string) (*Credentials, error) { 63 | c.serviceMutex.Lock() 64 | defer c.serviceMutex.Unlock() 65 | 66 | c.contentMutex.Lock() 67 | defer c.contentMutex.Unlock() 68 | 69 | if credentials, ok := c.credentials[token]; ok { 70 | return &credentials, nil 71 | } 72 | 73 | return nil, ErrCredentialsNotFound 74 | } 75 | 76 | func (c *credentialsServiceImpl) UpdateCredentials(awsKey, awsSecret, awsSession string, expiration time.Time) error { 77 | mapSize := len(c.credentials) 78 | if mapSize != 1 { 79 | return fmt.Errorf("there are %d set of credentials", mapSize) 80 | } 81 | 82 | var token string 83 | for key := range c.credentials { 84 | token = key 85 | } 86 | 87 | c.SetCredentials(token, awsKey, awsSecret, awsSession, expiration) 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /lambda/core/credentials_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | const ( 13 | Token string = "sampleToken" 14 | AwsKey string = "sampleKey" 15 | AwsSecret string = "sampleSecret" 16 | AwsSession string = "sampleSession" 17 | ) 18 | 19 | func TestGetSetCredentialsHappy(t *testing.T) { 20 | credentialsService := NewCredentialsService() 21 | 22 | credentialsExpiration := time.Now().Add(15 * time.Minute) 23 | credentialsService.SetCredentials(Token, AwsKey, AwsSecret, AwsSession, credentialsExpiration) 24 | 25 | credentials, err := credentialsService.GetCredentials(Token) 26 | 27 | assert.NoError(t, err) 28 | assert.Equal(t, AwsKey, credentials.AwsKey) 29 | assert.Equal(t, AwsSecret, credentials.AwsSecret) 30 | assert.Equal(t, AwsSession, credentials.AwsSession) 31 | } 32 | 33 | func TestGetCredentialsFail(t *testing.T) { 34 | credentialsService := NewCredentialsService() 35 | 36 | _, err := credentialsService.GetCredentials("unknownToken") 37 | 38 | assert.Error(t, err) 39 | } 40 | 41 | func TestUpdateCredentialsHappy(t *testing.T) { 42 | credentialsService := NewCredentialsService() 43 | 44 | credentialsExpiration := time.Now().Add(15 * time.Minute) 45 | credentialsService.SetCredentials(Token, AwsKey, AwsSecret, AwsSession, credentialsExpiration) 46 | 47 | restoreCredentialsExpiration := time.Now().Add(10 * time.Hour) 48 | 49 | err := credentialsService.UpdateCredentials("sampleKey1", "sampleSecret1", "sampleSession1", restoreCredentialsExpiration) 50 | assert.NoError(t, err) 51 | 52 | credentials, err := credentialsService.GetCredentials(Token) 53 | 54 | assert.NoError(t, err) 55 | assert.Equal(t, "sampleKey1", credentials.AwsKey) 56 | assert.Equal(t, "sampleSecret1", credentials.AwsSecret) 57 | assert.Equal(t, "sampleSession1", credentials.AwsSession) 58 | 59 | nineHoursLater := time.Now().Add(9 * time.Hour) 60 | 61 | assert.True(t, nineHoursLater.Before(credentials.Expiration)) 62 | } 63 | 64 | func TestUpdateCredentialsFail(t *testing.T) { 65 | credentialsService := NewCredentialsService() 66 | 67 | err := credentialsService.UpdateCredentials("unknownKey", "unknownSecret", "unknownSession", time.Now()) 68 | 69 | assert.Error(t, err) 70 | } 71 | -------------------------------------------------------------------------------- /lambda/core/directinvoke/customerheaders.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package directinvoke 5 | 6 | import ( 7 | "bytes" 8 | "encoding/base64" 9 | "encoding/json" 10 | ) 11 | 12 | type CustomerHeaders struct { 13 | CognitoIdentityID string `json:"Cognito-Identity-Id"` 14 | CognitoIdentityPoolID string `json:"Cognito-Identity-Pool-Id"` 15 | ClientContext string `json:"Client-Context"` 16 | } 17 | 18 | func (s CustomerHeaders) Dump() string { 19 | if (s == CustomerHeaders{}) { 20 | return "" 21 | } 22 | 23 | custHeadersJSON, err := json.Marshal(&s) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | return base64.StdEncoding.EncodeToString(custHeadersJSON) 29 | } 30 | 31 | func (s *CustomerHeaders) Load(in string) error { 32 | *s = CustomerHeaders{} 33 | 34 | if in == "" { 35 | return nil 36 | } 37 | 38 | base64Decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewReader([]byte(in))) 39 | 40 | return json.NewDecoder(base64Decoder).Decode(s) 41 | } 42 | -------------------------------------------------------------------------------- /lambda/core/directinvoke/customerheaders_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package directinvoke 5 | 6 | import ( 7 | "github.com/stretchr/testify/require" 8 | "testing" 9 | ) 10 | 11 | func TestCustomerHeadersEmpty(t *testing.T) { 12 | in := CustomerHeaders{} 13 | out := CustomerHeaders{} 14 | 15 | require.NoError(t, out.Load(in.Dump())) 16 | require.Equal(t, in, out) 17 | } 18 | 19 | func TestCustomerHeaders(t *testing.T) { 20 | in := CustomerHeaders{CognitoIdentityID: "asd"} 21 | out := CustomerHeaders{} 22 | 23 | require.NoError(t, out.Load(in.Dump())) 24 | require.Equal(t, in, out) 25 | } 26 | -------------------------------------------------------------------------------- /lambda/core/directinvoke/util.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package directinvoke 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "go.amzn.com/lambda/core/bandwidthlimiter" 10 | "io" 11 | "net/http" 12 | "time" 13 | 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | const DefaultRefillIntervalMs = 125 // default refill interval in milliseconds 18 | 19 | func NewStreamedResponseWriter(w http.ResponseWriter) (*bandwidthlimiter.BandwidthLimitingWriter, context.CancelFunc, error) { 20 | flushingWriter, err := NewFlushingWriter(w) // after writing a chunk we have to flush it to avoid additional buffering by ResponseWriter 21 | if err != nil { 22 | return nil, nil, err 23 | } 24 | cancellableWriter, cancel := NewCancellableWriter(flushingWriter) // cancelling prevents next calls to Write() from happening 25 | 26 | refillNumber := ResponseBandwidthRate * DefaultRefillIntervalMs / 1000 // refillNumber is calculated based on 'ResponseBandwidthRate' and bucket refill interval 27 | refillInterval := DefaultRefillIntervalMs * time.Millisecond 28 | 29 | // Initial bucket for token bucket algorithm allows for a burst of up to 6 MiB, and an average transmission rate of 2 MiB/s 30 | bucket, err := bandwidthlimiter.NewBucket(ResponseBandwidthBurstSize, ResponseBandwidthBurstSize, refillNumber, refillInterval) 31 | if err != nil { 32 | cancel() // free resources 33 | return nil, nil, err 34 | } 35 | 36 | bandwidthLimitingWriter, err := bandwidthlimiter.NewBandwidthLimitingWriter(cancellableWriter, bucket) 37 | if err != nil { 38 | cancel() // free resources 39 | return nil, nil, err 40 | } 41 | 42 | return bandwidthLimitingWriter, cancel, nil 43 | } 44 | 45 | func NewFlushingWriter(w io.Writer) (*FlushingWriter, error) { 46 | flusher, ok := w.(http.Flusher) 47 | if !ok { 48 | errorMsg := "expected http.ResponseWriter to be an http.Flusher" 49 | log.Error(errorMsg) 50 | return nil, errors.New(errorMsg) 51 | } 52 | return &FlushingWriter{ 53 | w: w, 54 | flusher: flusher, 55 | }, nil 56 | } 57 | 58 | type FlushingWriter struct { 59 | w io.Writer 60 | flusher http.Flusher 61 | } 62 | 63 | func (w *FlushingWriter) Write(p []byte) (n int, err error) { 64 | n, err = w.w.Write(p) 65 | w.flusher.Flush() 66 | return 67 | } 68 | 69 | func NewCancellableWriter(w io.Writer) (*CancellableWriter, context.CancelFunc) { 70 | ctx, cancel := context.WithCancel(context.Background()) 71 | return &CancellableWriter{w: w, ctx: ctx}, cancel 72 | } 73 | 74 | type CancellableWriter struct { 75 | w io.Writer 76 | ctx context.Context 77 | } 78 | 79 | func (w *CancellableWriter) Write(p []byte) (int, error) { 80 | if err := w.ctx.Err(); err != nil { 81 | return 0, err 82 | } 83 | return w.w.Write(p) 84 | } 85 | -------------------------------------------------------------------------------- /lambda/core/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /* 5 | Package core provides state objects and synchronization primitives for 6 | managing data flow in the system. 7 | 8 | # States 9 | 10 | Runtime and Agent implement state object design pattern. 11 | 12 | Runtime state interface: 13 | 14 | type RuntimeState interface { 15 | InitError() error 16 | Ready() error 17 | InvocationResponse() error 18 | InvocationErrorResponse() error 19 | } 20 | 21 | # Gates 22 | 23 | Gates provide synchornization primitives for managing data flow in the system. 24 | 25 | Gate is a synchronization aid that allows one or more threads to wait until a 26 | set of operations being performed in other threads completes. 27 | 28 | To better understand gates, consider two examples below: 29 | 30 | Example 1: main thread is awaiting registered threads to walk through the gate, 31 | 32 | and after the last registered thread walked through the gate, gate 33 | condition will be satisfied and main thread will proceed: 34 | 35 | [main] // register threads with the gate and start threads ... 36 | [main] g.AwaitGateCondition() 37 | [main] // blocked until gate condition is satisfied 38 | 39 | [thread] g.WalkThrough() 40 | [thread] // not blocked 41 | 42 | Example 2: main thread is awaiting registered threads to arrive at the gate, 43 | 44 | and after the last registered thread arrives at the gate, gate 45 | condition will be satisfied and main thread, along with registered 46 | threads will proceed: 47 | 48 | [main] // register threads with the gate and start threads ... 49 | [main] g.AwaitGateCondition() 50 | [main] // blocked until gate condition is satisfied 51 | 52 | # Flow 53 | 54 | Flow wraps a set of specific gates required to implement specific data flow in the system. 55 | 56 | Example flows would be INIT, INVOKE and RESET. 57 | 58 | # Registrations 59 | 60 | Registration service manages registrations, it maintains the mapping between registered 61 | parties are events they are registered. Parties not registered in the system will not 62 | be issued events. 63 | */ 64 | package core 65 | -------------------------------------------------------------------------------- /lambda/core/gates.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | import ( 7 | "errors" 8 | "math" 9 | "sync" 10 | ) 11 | 12 | const maxAgentsLimit uint16 = math.MaxUint16 13 | 14 | // Gate ... 15 | type Gate interface { 16 | Register(count uint16) 17 | Reset() 18 | SetCount(uint16) error 19 | WalkThrough() error 20 | AwaitGateCondition() error 21 | CancelWithError(error) 22 | Clear() 23 | } 24 | 25 | type gateImpl struct { 26 | count uint16 27 | arrived uint16 28 | gateCondition *sync.Cond 29 | canceled bool 30 | err error 31 | } 32 | 33 | func (g *gateImpl) Register(count uint16) { 34 | g.gateCondition.L.Lock() 35 | defer g.gateCondition.L.Unlock() 36 | g.count += count 37 | } 38 | 39 | // SetCount sets the expected number of arrivals on the gate 40 | func (g *gateImpl) SetCount(count uint16) error { 41 | g.gateCondition.L.Lock() 42 | defer g.gateCondition.L.Unlock() 43 | // you can't set count larger than limit if limit is max uint but leaving it here for correctness in case limit changes 44 | if count > maxAgentsLimit || count < g.arrived { 45 | return ErrGateIntegrity 46 | } 47 | g.count = count 48 | return nil 49 | } 50 | 51 | func (g *gateImpl) Reset() { 52 | g.gateCondition.L.Lock() 53 | defer g.gateCondition.L.Unlock() 54 | if !g.canceled { 55 | g.arrived = 0 56 | } 57 | } 58 | 59 | // ErrGateIntegrity ... 60 | var ErrGateIntegrity = errors.New("ErrGateIntegrity") 61 | 62 | // ErrGateCanceled ... 63 | var ErrGateCanceled = errors.New("ErrGateCanceled") 64 | 65 | // WalkThrough walks through this gate without awaiting others. 66 | func (g *gateImpl) WalkThrough() error { 67 | g.gateCondition.L.Lock() 68 | defer g.gateCondition.L.Unlock() 69 | 70 | if g.arrived == g.count { 71 | return ErrGateIntegrity 72 | } 73 | 74 | g.arrived++ 75 | 76 | if g.arrived == g.count { 77 | g.gateCondition.Broadcast() 78 | } 79 | 80 | return nil 81 | } 82 | 83 | // AwaitGateCondition suspends thread execution until gate condition 84 | // is met or await is canceled via Cancel method. 85 | func (g *gateImpl) AwaitGateCondition() error { 86 | g.gateCondition.L.Lock() 87 | defer g.gateCondition.L.Unlock() 88 | 89 | for g.arrived != g.count && !g.canceled { 90 | g.gateCondition.Wait() 91 | } 92 | 93 | if g.canceled { 94 | if g.err != nil { 95 | return g.err 96 | } 97 | return ErrGateCanceled 98 | } 99 | 100 | return nil 101 | } 102 | 103 | // CancelWithError cancels gate condition with error and awakes suspended threads. 104 | func (g *gateImpl) CancelWithError(err error) { 105 | g.gateCondition.L.Lock() 106 | defer g.gateCondition.L.Unlock() 107 | g.canceled = true 108 | g.err = err 109 | g.gateCondition.Broadcast() 110 | } 111 | 112 | // Clear gate state 113 | func (g *gateImpl) Clear() { 114 | g.gateCondition.L.Lock() 115 | defer g.gateCondition.L.Unlock() 116 | 117 | g.canceled = false 118 | g.arrived = 0 119 | g.err = nil 120 | } 121 | 122 | // NewGate returns new gate instance. 123 | func NewGate(count uint16) Gate { 124 | return &gateImpl{ 125 | count: count, 126 | gateCondition: sync.NewCond(&sync.Mutex{}), 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lambda/core/gates_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | import ( 7 | "errors" 8 | "github.com/stretchr/testify/assert" 9 | "golang.org/x/sync/errgroup" 10 | "testing" 11 | ) 12 | 13 | func TestWalkThrough(t *testing.T) { 14 | g := NewGate(1) 15 | assert.NoError(t, g.WalkThrough()) 16 | } 17 | 18 | func TestWalkThroughTwice(t *testing.T) { 19 | g := NewGate(1) 20 | assert.NoError(t, g.WalkThrough()) 21 | assert.Equal(t, ErrGateIntegrity, g.WalkThrough()) 22 | } 23 | 24 | func TestSetCount(t *testing.T) { 25 | g := NewGate(2) 26 | assert.NoError(t, g.WalkThrough()) 27 | assert.NoError(t, g.WalkThrough()) // arrived is now 2 28 | assert.Equal(t, ErrGateIntegrity, g.SetCount(1)) 29 | assert.NoError(t, g.SetCount(2)) // set to 2 30 | assert.Equal(t, ErrGateIntegrity, g.WalkThrough()) // can't go to 3 31 | assert.NoError(t, g.SetCount(3)) // set to 3 32 | assert.NoError(t, g.WalkThrough()) 33 | } 34 | 35 | func TestReset(t *testing.T) { 36 | g := NewGate(1) 37 | assert.NoError(t, g.WalkThrough()) 38 | g.Reset() 39 | assert.NoError(t, g.WalkThrough()) 40 | } 41 | 42 | func TestCancel(t *testing.T) { 43 | g := NewGate(1) 44 | 45 | var errg errgroup.Group 46 | errg.Go(g.AwaitGateCondition) 47 | g.CancelWithError(nil) 48 | 49 | assert.Equal(t, ErrGateCanceled, errg.Wait()) 50 | } 51 | 52 | func TestCancelWithError(t *testing.T) { 53 | g := NewGate(1) 54 | 55 | var errg errgroup.Group 56 | errg.Go(g.AwaitGateCondition) 57 | 58 | err := errors.New("MyErr") 59 | g.CancelWithError(err) 60 | 61 | assert.Equal(t, err, errg.Wait()) 62 | } 63 | 64 | func TestUseAfterCancel(t *testing.T) { 65 | g := NewGate(1) 66 | err := errors.New("MyErr") 67 | g.CancelWithError(err) 68 | assert.Equal(t, err, g.AwaitGateCondition()) 69 | g.Reset() 70 | assert.Equal(t, err, g.AwaitGateCondition()) 71 | } 72 | 73 | func BenchmarkAwaitGateCondition(b *testing.B) { 74 | g := NewGate(1) 75 | 76 | for n := 0; n < b.N; n++ { 77 | go func() { g.WalkThrough() }() 78 | if err := g.AwaitGateCondition(); err != nil { 79 | panic(err) 80 | } 81 | g.Reset() 82 | } 83 | } 84 | 85 | // go test -run=XXX -bench=. -benchtime 10000000x -cpu 1 -blockprofile /tmp/pprof/block3.out src/go.amzn.com/lambda/core/* 86 | // goos: linux 87 | // goarch: amd64 88 | // BenchmarkAwaitGateCondition 10000000 1834 ns/op 89 | // PASS 90 | // ok command-line-arguments 18.449s 91 | -------------------------------------------------------------------------------- /lambda/core/registrations_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestRegistrationServiceHappyPathDuringInit(t *testing.T) { 13 | // Setup 14 | initFlow, invokeFlow := NewInitFlowSynchronization(), NewInvokeFlowSynchronization() 15 | registrationService := NewRegistrationService(initFlow, invokeFlow) 16 | 17 | registrationService.SetFunctionMetadata(FunctionMetadata{ 18 | FunctionName: "AWS_LAMBDA_FUNCTION_NAME", 19 | FunctionVersion: "AWS_LAMBDA_FUNCTION_VERSION", 20 | Handler: "_HANDLER", 21 | }) 22 | 23 | // Extension INIT (external) 24 | extAgentNames := []string{"agentName1", "agentName2"} 25 | assert.NoError(t, initFlow.SetExternalAgentsRegisterCount(uint16(len(extAgentNames)))) 26 | 27 | extAgent1, err := registrationService.CreateExternalAgent(extAgentNames[0]) 28 | assert.NoError(t, err) 29 | 30 | extAgent2, err := registrationService.CreateExternalAgent(extAgentNames[1]) 31 | assert.NoError(t, err) 32 | 33 | go func() { 34 | for _, agentName := range extAgentNames { 35 | agent, found := registrationService.FindExternalAgentByName(agentName) 36 | assert.True(t, found) 37 | 38 | assert.NoError(t, agent.Register([]Event{InvokeEvent, ShutdownEvent})) 39 | } 40 | }() 41 | 42 | assert.NoError(t, initFlow.AwaitExternalAgentsRegistered()) 43 | 44 | // Runtime INIT (+ internal extensions) 45 | runtime := NewRuntime(initFlow, invokeFlow) 46 | assert.NoError(t, registrationService.PreregisterRuntime(runtime)) 47 | 48 | intAgentNames := []string{"intAgentName1", "intAgentName2"} 49 | 50 | intAgent1, err := registrationService.CreateInternalAgent(intAgentNames[0]) 51 | assert.NoError(t, err) 52 | 53 | intAgent2, err := registrationService.CreateInternalAgent(intAgentNames[1]) 54 | assert.NoError(t, err) 55 | 56 | go func() { 57 | for _, agentName := range intAgentNames { 58 | agent, found := registrationService.FindInternalAgentByName(agentName) 59 | assert.True(t, found) 60 | 61 | assert.NoError(t, agent.Register([]Event{InvokeEvent})) 62 | } 63 | assert.NoError(t, runtime.Ready()) 64 | }() 65 | 66 | assert.NoError(t, initFlow.AwaitRuntimeRestoreReady()) 67 | registrationService.TurnOff() 68 | 69 | // Agents Ready 70 | 71 | assert.NoError(t, initFlow.SetAgentsReadyCount(registrationService.GetRegisteredAgentsSize())) 72 | go func() { 73 | for _, agentName := range intAgentNames { 74 | agent, found := registrationService.FindInternalAgentByName(agentName) 75 | assert.True(t, found) 76 | go func() { assert.NoError(t, agent.Ready()) }() 77 | } 78 | 79 | for _, agentName := range extAgentNames { 80 | agent, found := registrationService.FindExternalAgentByName(agentName) 81 | assert.True(t, found) 82 | go func() { assert.NoError(t, agent.Ready()) }() 83 | } 84 | }() 85 | 86 | assert.NoError(t, initFlow.AwaitAgentsReady()) 87 | 88 | // Assertions 89 | expectedAgents := []AgentInfo{ 90 | AgentInfo{extAgent1.Name, "Ready", []string{"INVOKE", "SHUTDOWN"}, ""}, 91 | AgentInfo{extAgent2.Name, "Ready", []string{"INVOKE", "SHUTDOWN"}, ""}, 92 | AgentInfo{intAgent1.Name, "Ready", []string{"INVOKE"}, ""}, 93 | AgentInfo{intAgent2.Name, "Ready", []string{"INVOKE"}, ""}, 94 | } 95 | 96 | assert.Len(t, registrationService.AgentsInfo(), len(expectedAgents)) 97 | 98 | actualAgents := map[string]AgentInfo{} 99 | for _, agentInfo := range registrationService.AgentsInfo() { 100 | actualAgents[agentInfo.Name] = agentInfo 101 | } 102 | 103 | for _, agentInfo := range expectedAgents { 104 | assert.Contains(t, actualAgents, agentInfo.Name) 105 | assert.Equal(t, actualAgents[agentInfo.Name].Name, agentInfo.Name) 106 | assert.Equal(t, actualAgents[agentInfo.Name].State, agentInfo.State) 107 | for _, event := range agentInfo.Subscriptions { 108 | assert.Contains(t, actualAgents[agentInfo.Name].Subscriptions, event) 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lambda/core/runtime_state_names.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package core 5 | 6 | // String values of possibles runtime states 7 | const ( 8 | RuntimeStartedStateName = "Started" 9 | RuntimeInitErrorStateName = "InitError" 10 | RuntimeReadyStateName = "Ready" 11 | RuntimeRunningStateName = "Running" 12 | // RuntimeStartedState -> RuntimeRestoreReadyState 13 | RuntimeRestoreReadyStateName = "RestoreReady" 14 | // RuntimeRestoreReadyState -> RuntimeRestoringState 15 | RuntimeRestoringStateName = "Restoring" 16 | RuntimeInvocationResponseStateName = "InvocationResponse" 17 | RuntimeInvocationErrorResponseStateName = "InvocationErrorResponse" 18 | RuntimeResponseSentStateName = "RuntimeResponseSentState" 19 | RuntimeRestoreErrorStateName = "RuntimeRestoreErrorState" 20 | ) 21 | -------------------------------------------------------------------------------- /lambda/core/statejson/description.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package statejson 5 | 6 | import ( 7 | "encoding/json" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // ResponseMode are top-level constants used in combination with the various types of 13 | // modes we have for responses, such as invoke's response mode and function's response mode. 14 | // In the future we might have invoke's request mode or similar, so these help set the ground 15 | // for consistency. 16 | type ResponseMode string 17 | 18 | const ResponseModeBuffered = "Buffered" 19 | const ResponseModeStreaming = "Streaming" 20 | 21 | type InvokeResponseMode string 22 | 23 | const InvokeResponseModeBuffered InvokeResponseMode = ResponseModeBuffered 24 | const InvokeResponseModeStreaming InvokeResponseMode = ResponseModeStreaming 25 | 26 | // StateDescription ... 27 | type StateDescription struct { 28 | Name string `json:"name"` 29 | LastModified int64 `json:"lastModified"` 30 | ResponseTimeNs int64 `json:"responseTimeNs"` 31 | } 32 | 33 | // RuntimeDescription ... 34 | type RuntimeDescription struct { 35 | State StateDescription `json:"state"` 36 | } 37 | 38 | // ExtensionDescription ... 39 | type ExtensionDescription struct { 40 | Name string `json:"name"` 41 | ID string 42 | State StateDescription `json:"state"` 43 | ErrorType string `json:"errorType"` 44 | } 45 | 46 | // InternalStateDescription describes internal state of runtime and extensions for debugging purposes 47 | type InternalStateDescription struct { 48 | Runtime *RuntimeDescription `json:"runtime"` 49 | Extensions []ExtensionDescription `json:"extensions"` 50 | FirstFatalError string `json:"firstFatalError"` 51 | } 52 | 53 | type ResponseMetricsDimensions struct { 54 | InvokeResponseMode InvokeResponseMode `json:"invokeResponseMode"` 55 | } 56 | 57 | type ResponseMetrics struct { 58 | RuntimeResponseLatencyMs float64 `json:"runtimeResponseLatencyMs"` 59 | Dimensions ResponseMetricsDimensions `json:"dimensions"` 60 | } 61 | 62 | type ReleaseResponse struct { 63 | *InternalStateDescription 64 | ResponseMetrics ResponseMetrics `json:"responseMetrics"` 65 | } 66 | 67 | // ResetDescription describes fields of the response to an INVOKE API request 68 | type ResetDescription struct { 69 | ExtensionsResetMs int64 `json:"extensionsResetMs"` 70 | ResponseMetrics ResponseMetrics `json:"responseMetrics"` 71 | } 72 | 73 | func (s *InternalStateDescription) AsJSON() []byte { 74 | bytes, err := json.Marshal(s) 75 | if err != nil { 76 | log.Panicf("Failed to marshall internal states: %s", err) 77 | } 78 | return bytes 79 | } 80 | 81 | func (s *ResetDescription) AsJSON() []byte { 82 | bytes, err := json.Marshal(s) 83 | if err != nil { 84 | log.Panicf("Failed to marshall reset description: %s", err) 85 | } 86 | return bytes 87 | } 88 | 89 | func (s *ReleaseResponse) AsJSON() []byte { 90 | bytes, err := json.Marshal(s) 91 | if err != nil { 92 | log.Panicf("Failed to marshall release response: %s", err) 93 | } 94 | return bytes 95 | } 96 | -------------------------------------------------------------------------------- /lambda/extensions/extensions.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package extensions 5 | 6 | import ( 7 | "os" 8 | "sync/atomic" 9 | 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | const ( 14 | disableExtensionsFile = "/opt/disable-extensions-jwigqn8j" 15 | ) 16 | 17 | var enabled atomic.Value 18 | 19 | // Enable or disable extensions 20 | func Enable() { 21 | enabled.Store(true) 22 | } 23 | 24 | func Disable() { 25 | enabled.Store(false) 26 | } 27 | 28 | // AreEnabled returns true if extensions are enabled, false otherwise 29 | // If it was never set defaults to false 30 | func AreEnabled() bool { 31 | val := enabled.Load() 32 | if nil == val { 33 | return false 34 | } 35 | return val.(bool) 36 | } 37 | 38 | func DisableViaMagicLayer() { 39 | _, err := os.Stat(disableExtensionsFile) 40 | if err == nil { 41 | log.Infof("Extensions disabled by attached layer (%s)", disableExtensionsFile) 42 | Disable() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lambda/fatalerror/fatalerror.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package fatalerror 5 | 6 | import ( 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | // This package defines constant error types returned to slicer with DONE(failure), and also sandbox errors 12 | // Separate package for namespacing 13 | 14 | // ErrorType is returned to slicer inside DONE 15 | type ErrorType string 16 | 17 | // TODO: Find another name than "fatalerror" 18 | // TODO: Rename all const so that they always begin with Agent/Runtime/Sandbox/Function 19 | // TODO: Add filtering for extensions as well 20 | const ( 21 | // Extension errors 22 | AgentInitError ErrorType = "Extension.InitError" // agent exited after calling /extension/init/error 23 | AgentExitError ErrorType = "Extension.ExitError" // agent exited after calling /extension/exit/error 24 | AgentCrash ErrorType = "Extension.Crash" // agent crashed unexpectedly 25 | AgentLaunchError ErrorType = "Extension.LaunchError" // agent could not be launched 26 | 27 | // Runtime errors 28 | RuntimeExit ErrorType = "Runtime.ExitError" 29 | InvalidEntrypoint ErrorType = "Runtime.InvalidEntrypoint" 30 | InvalidWorkingDir ErrorType = "Runtime.InvalidWorkingDir" 31 | InvalidTaskConfig ErrorType = "Runtime.InvalidTaskConfig" 32 | TruncatedResponse ErrorType = "Runtime.TruncatedResponse" 33 | RuntimeInvalidResponseModeHeader ErrorType = "Runtime.InvalidResponseModeHeader" 34 | RuntimeUnknown ErrorType = "Runtime.Unknown" 35 | 36 | // Function errors 37 | FunctionOversizedResponse ErrorType = "Function.ResponseSizeTooLarge" 38 | FunctionUnknown ErrorType = "Function.Unknown" 39 | 40 | // Sandbox errors 41 | SandboxFailure ErrorType = "Sandbox.Failure" 42 | SandboxTimeout ErrorType = "Sandbox.Timeout" 43 | ) 44 | 45 | var validRuntimeAndFunctionErrors = map[ErrorType]struct{}{ 46 | // Runtime errors 47 | RuntimeExit: {}, 48 | InvalidEntrypoint: {}, 49 | InvalidWorkingDir: {}, 50 | InvalidTaskConfig: {}, 51 | TruncatedResponse: {}, 52 | RuntimeInvalidResponseModeHeader: {}, 53 | RuntimeUnknown: {}, 54 | 55 | // Function errors 56 | FunctionOversizedResponse: {}, 57 | FunctionUnknown: {}, 58 | } 59 | 60 | func GetValidRuntimeOrFunctionErrorType(errorType string) ErrorType { 61 | match, _ := regexp.MatchString("(Runtime|Function)\\.[A-Z][a-zA-Z]+", errorType) 62 | if match { 63 | return ErrorType(errorType) 64 | } 65 | 66 | if strings.HasPrefix(errorType, "Function.") { 67 | return FunctionUnknown 68 | } 69 | 70 | return RuntimeUnknown 71 | } 72 | -------------------------------------------------------------------------------- /lambda/fatalerror/fatalerror_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package fatalerror 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestValidRuntimeAndFunctionErrors(t *testing.T) { 14 | type test struct { 15 | input string 16 | expected ErrorType 17 | } 18 | 19 | var tests = []test{} 20 | for validError := range validRuntimeAndFunctionErrors { 21 | tests = append(tests, test{input: string(validError), expected: validError}) 22 | } 23 | 24 | for _, tt := range tests { 25 | t.Run(tt.input, func(t *testing.T) { 26 | assert.Equal(t, GetValidRuntimeOrFunctionErrorType(tt.input), tt.expected) 27 | }) 28 | } 29 | } 30 | 31 | func TestGetValidRuntimeOrFunctionErrorType(t *testing.T) { 32 | type test struct { 33 | input string 34 | expected ErrorType 35 | } 36 | 37 | var tests = []test{ 38 | {"", RuntimeUnknown}, 39 | {"MyCustomError", RuntimeUnknown}, 40 | {"MyCustomError.Error", RuntimeUnknown}, 41 | {"Runtime.MyCustomErrorTypeHere", ErrorType("Runtime.MyCustomErrorTypeHere")}, 42 | {"Function.MyCustomErrorTypeHere", ErrorType("Function.MyCustomErrorTypeHere")}, 43 | } 44 | 45 | for _, tt := range tests { 46 | testname := fmt.Sprintf("TestGetValidRuntimeOrFunctionErrorType with %s", tt.input) 47 | t.Run(testname, func(t *testing.T) { 48 | assert.Equal(t, GetValidRuntimeOrFunctionErrorType(tt.input), tt.expected) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lambda/interop/bootstrap.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package interop 5 | 6 | import ( 7 | "os" 8 | 9 | "go.amzn.com/lambda/fatalerror" 10 | "go.amzn.com/lambda/rapidcore/env" 11 | ) 12 | 13 | type Bootstrap interface { 14 | Cmd() ([]string, error) // returns the args of bootstrap, where args[0] is the path to executable 15 | Env(e *env.Environment) map[string]string // returns the environment variables to be passed to the bootstrapped process 16 | Cwd() (string, error) // returns the working directory of the bootstrap process 17 | ExtraFiles() []*os.File // returns the extra file descriptors apart from 1 & 2 to be passed to runtime 18 | CachedFatalError(err error) (fatalerror.ErrorType, string, bool) 19 | } 20 | -------------------------------------------------------------------------------- /lambda/interop/cancellable_request.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package interop 5 | 6 | import ( 7 | "net" 8 | "net/http" 9 | ) 10 | 11 | type key int 12 | 13 | const ( 14 | HTTPConnKey key = iota 15 | ) 16 | 17 | func GetConn(r *http.Request) net.Conn { 18 | return r.Context().Value(HTTPConnKey).(net.Conn) 19 | } 20 | 21 | type CancellableRequest struct { 22 | Request *http.Request 23 | } 24 | 25 | func (c *CancellableRequest) Cancel() error { 26 | return GetConn(c.Request).Close() 27 | } 28 | -------------------------------------------------------------------------------- /lambda/interop/messages.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package interop 5 | 6 | // conversion from internal data structure into well defined messages 7 | 8 | func DoneFromInvokeSuccess(successMsg InvokeSuccess) *Done { 9 | return &Done{ 10 | Meta: DoneMetadata{ 11 | RuntimeRelease: successMsg.RuntimeRelease, 12 | NumActiveExtensions: successMsg.NumActiveExtensions, 13 | ExtensionNames: successMsg.ExtensionNames, 14 | InvokeRequestReadTimeNs: successMsg.InvokeMetrics.InvokeRequestReadTimeNs, 15 | InvokeRequestSizeBytes: successMsg.InvokeMetrics.InvokeRequestSizeBytes, 16 | RuntimeReadyTime: successMsg.InvokeMetrics.RuntimeReadyTime, 17 | 18 | InvokeCompletionTimeNs: successMsg.InvokeCompletionTimeNs, 19 | InvokeReceivedTime: successMsg.InvokeReceivedTime, 20 | RuntimeResponseLatencyMs: successMsg.ResponseMetrics.RuntimeResponseLatencyMs, 21 | RuntimeTimeThrottledMs: successMsg.ResponseMetrics.RuntimeTimeThrottledMs, 22 | RuntimeProducedBytes: successMsg.ResponseMetrics.RuntimeProducedBytes, 23 | RuntimeOutboundThroughputBps: successMsg.ResponseMetrics.RuntimeOutboundThroughputBps, 24 | LogsAPIMetrics: successMsg.LogsAPIMetrics, 25 | MetricsDimensions: DoneMetadataMetricsDimensions{ 26 | InvokeResponseMode: successMsg.InvokeResponseMode, 27 | }, 28 | }, 29 | } 30 | } 31 | 32 | func DoneFailFromInvokeFailure(failureMsg *InvokeFailure) *DoneFail { 33 | return &DoneFail{ 34 | ErrorType: failureMsg.ErrorType, 35 | Meta: DoneMetadata{ 36 | RuntimeRelease: failureMsg.RuntimeRelease, 37 | NumActiveExtensions: failureMsg.NumActiveExtensions, 38 | InvokeReceivedTime: failureMsg.InvokeReceivedTime, 39 | 40 | RuntimeResponseLatencyMs: failureMsg.ResponseMetrics.RuntimeResponseLatencyMs, 41 | RuntimeTimeThrottledMs: failureMsg.ResponseMetrics.RuntimeTimeThrottledMs, 42 | RuntimeProducedBytes: failureMsg.ResponseMetrics.RuntimeProducedBytes, 43 | RuntimeOutboundThroughputBps: failureMsg.ResponseMetrics.RuntimeOutboundThroughputBps, 44 | 45 | InvokeRequestReadTimeNs: failureMsg.InvokeMetrics.InvokeRequestReadTimeNs, 46 | InvokeRequestSizeBytes: failureMsg.InvokeMetrics.InvokeRequestSizeBytes, 47 | RuntimeReadyTime: failureMsg.InvokeMetrics.RuntimeReadyTime, 48 | 49 | ExtensionNames: failureMsg.ExtensionNames, 50 | LogsAPIMetrics: failureMsg.LogsAPIMetrics, 51 | 52 | MetricsDimensions: DoneMetadataMetricsDimensions{ 53 | InvokeResponseMode: failureMsg.InvokeResponseMode, 54 | }, 55 | }, 56 | } 57 | } 58 | 59 | func DoneFailFromInitFailure(initFailure *InitFailure) *DoneFail { 60 | return &DoneFail{ 61 | ErrorType: initFailure.ErrorType, 62 | Meta: DoneMetadata{ 63 | RuntimeRelease: initFailure.RuntimeRelease, 64 | NumActiveExtensions: initFailure.NumActiveExtensions, 65 | LogsAPIMetrics: initFailure.LogsAPIMetrics, 66 | }, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lambda/interop/model_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package interop 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "go.amzn.com/lambda/fatalerror" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestMergeSubscriptionMetrics(t *testing.T) { 16 | logsAPIMetrics := map[string]int{ 17 | "server_error": 1, 18 | "client_error": 2, 19 | } 20 | 21 | telemetryAPIMetrics := map[string]int{ 22 | "server_error": 1, 23 | "success": 5, 24 | } 25 | 26 | metrics := MergeSubscriptionMetrics(logsAPIMetrics, telemetryAPIMetrics) 27 | assert.Equal(t, 5, metrics["success"]) 28 | assert.Equal(t, 2, metrics["server_error"]) 29 | assert.Equal(t, 2, metrics["client_error"]) 30 | } 31 | 32 | func TestGetErrorResponseWithFormattedErrorMessageWithoutInvokeRequestId(t *testing.T) { 33 | errorType := fatalerror.RuntimeExit 34 | errorMessage := fmt.Errorf("Divided by 0") 35 | expectedMsg := fmt.Sprintf(`Error: %s`, errorMessage) 36 | expectedJSON := fmt.Sprintf(`{"errorType": "%s", "errorMessage": "%s"}`, string(errorType), expectedMsg) 37 | 38 | actual := GetErrorResponseWithFormattedErrorMessage(errorType, errorMessage, "") 39 | assert.Equal(t, errorType, actual.FunctionError.Type) 40 | assert.Equal(t, expectedMsg, actual.FunctionError.Message) 41 | assert.JSONEq(t, expectedJSON, string(actual.Payload)) 42 | } 43 | 44 | func TestGetErrorResponseWithFormattedErrorMessageWithInvokeRequestId(t *testing.T) { 45 | errorType := fatalerror.RuntimeExit 46 | errorMessage := fmt.Errorf("Divided by 0") 47 | invokeID := "invoke-id" 48 | expectedMsg := fmt.Sprintf(`RequestId: %s Error: %s`, invokeID, errorMessage) 49 | expectedJSON := fmt.Sprintf(`{"errorType": "%s", "errorMessage": "%s"}`, string(errorType), expectedMsg) 50 | 51 | actual := GetErrorResponseWithFormattedErrorMessage(errorType, errorMessage, invokeID) 52 | assert.Equal(t, errorType, actual.FunctionError.Type) 53 | assert.Equal(t, expectedMsg, actual.FunctionError.Message) 54 | assert.JSONEq(t, expectedJSON, string(actual.Payload)) 55 | } 56 | 57 | func TestDoneMetadataMetricsDimensionsStringWhenInvokeResponseModeIsPresent(t *testing.T) { 58 | dimensions := DoneMetadataMetricsDimensions{ 59 | InvokeResponseMode: InvokeResponseModeStreaming, 60 | } 61 | assert.Equal(t, "invoke_response_mode=streaming", dimensions.String()) 62 | } 63 | func TestDoneMetadataMetricsDimensionsStringWhenEmpty(t *testing.T) { 64 | dimensions := DoneMetadataMetricsDimensions{} 65 | assert.Equal(t, "", dimensions.String()) 66 | } 67 | -------------------------------------------------------------------------------- /lambda/logging/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /* 5 | RAPID emits or proxies the following sources of logging: 6 | 7 | 1. Internal logs: RAPID's own application logs into stderr for operational use, visible only internally 8 | 2. Function stream-based logs: Runtime's stdout and stderr, read as newline separated lines 9 | 3. Function message-based logs: Stock runtimes communicate using a custom TLV protocol over a Unix pipe 10 | 4. Extension stream-based logs: Extension's stdout and stderr, read as newline separated lines 11 | 5. Platform logs: Logs that RAPID generates, but is visible either in customer's logs or via Logs API 12 | (e.g. EXTENSION, RUNTIME, RUNTIMEDONE, IMAGE) 13 | */ 14 | package logging 15 | -------------------------------------------------------------------------------- /lambda/logging/internal_log.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package logging 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "github.com/sirupsen/logrus" 10 | "io" 11 | "log" 12 | "strings" 13 | ) 14 | 15 | // SetOutput configures logging output for standard loggers. 16 | func SetOutput(w io.Writer) { 17 | log.SetOutput(w) 18 | logrus.SetOutput(w) 19 | } 20 | 21 | type InternalFormatter struct{} 22 | 23 | // format RAPID's internal log like the rest of the sandbox log 24 | func (f *InternalFormatter) Format(entry *logrus.Entry) ([]byte, error) { 25 | b := &bytes.Buffer{} 26 | 27 | // time with comma separator for fraction of second 28 | time := entry.Time.Format("02 Jan 2006 15:04:05.000") 29 | time = strings.Replace(time, ".", ",", 1) 30 | fmt.Fprint(b, time) 31 | 32 | // level 33 | level := strings.ToUpper(entry.Level.String()) 34 | fmt.Fprintf(b, " [%s]", level) 35 | 36 | // label 37 | fmt.Fprint(b, " (rapid)") 38 | 39 | // message 40 | fmt.Fprintf(b, " %s", entry.Message) 41 | 42 | // from WithField and WithError 43 | for field, value := range entry.Data { 44 | fmt.Fprintf(b, " %s=%s", field, value) 45 | } 46 | 47 | fmt.Fprintf(b, "\n") 48 | return b.Bytes(), nil 49 | } 50 | -------------------------------------------------------------------------------- /lambda/metering/time.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package metering 5 | 6 | import ( 7 | _ "runtime" //for nanotime() and walltime() 8 | "time" 9 | _ "unsafe" //for go:linkname 10 | ) 11 | 12 | //go:linkname Monotime runtime.nanotime 13 | func Monotime() int64 14 | 15 | // MonoToEpoch converts monotonic time nanos to unix epoch time nanos. 16 | func MonoToEpoch(t int64) int64 { 17 | monoNsec := Monotime() 18 | wallNsec := time.Now().UnixNano() 19 | clockOffset := wallNsec - monoNsec 20 | return t + clockOffset 21 | } 22 | 23 | func TimeToMono(t time.Time) int64 { 24 | durNs := time.Since(t).Nanoseconds() 25 | return Monotime() - durNs 26 | } 27 | 28 | type ExtensionsResetDurationProfiler struct { 29 | NumAgentsRegisteredForShutdown int 30 | AvailableNs int64 31 | extensionsResetStartTimeNs int64 32 | extensionsResetEndTimeNs int64 33 | } 34 | 35 | func (p *ExtensionsResetDurationProfiler) Start() { 36 | p.extensionsResetStartTimeNs = Monotime() 37 | } 38 | 39 | func (p *ExtensionsResetDurationProfiler) Stop() { 40 | p.extensionsResetEndTimeNs = Monotime() 41 | } 42 | 43 | func (p *ExtensionsResetDurationProfiler) CalculateExtensionsResetMs() (int64, bool) { 44 | var extensionsResetDurationNs = p.extensionsResetEndTimeNs - p.extensionsResetStartTimeNs 45 | var extensionsResetMs int64 46 | timedOut := false 47 | 48 | if p.NumAgentsRegisteredForShutdown == 0 || p.AvailableNs < 0 || extensionsResetDurationNs < 0 { 49 | extensionsResetMs = 0 50 | } else if extensionsResetDurationNs > p.AvailableNs { 51 | extensionsResetMs = p.AvailableNs / time.Millisecond.Nanoseconds() 52 | timedOut = true 53 | } else { 54 | extensionsResetMs = extensionsResetDurationNs / time.Millisecond.Nanoseconds() 55 | } 56 | 57 | return extensionsResetMs, timedOut 58 | } 59 | -------------------------------------------------------------------------------- /lambda/metering/time_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package metering 5 | 6 | import ( 7 | "math" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestMonoToEpochPrecision(t *testing.T) { 15 | a := time.Now().UnixNano() 16 | b := MonoToEpoch(Monotime()) 17 | 18 | // Conversion error is less than a millisecond. 19 | assert.True(t, math.Abs(float64(a-b)) < float64(time.Millisecond)) 20 | } 21 | 22 | func TestEpochToMonoPrecision(t *testing.T) { 23 | a := Monotime() 24 | b := TimeToMono(time.Now()) 25 | 26 | // Conversion error is less than a millisecond. 27 | assert.Less(t, math.Abs(float64(b-a)), float64(1*time.Millisecond)) 28 | } 29 | 30 | func TestExtensionsResetDurationProfilerForExtensionsResetWithNoExtensions(t *testing.T) { 31 | mono := Monotime() 32 | profiler := ExtensionsResetDurationProfiler{} 33 | 34 | profiler.extensionsResetStartTimeNs = mono 35 | profiler.extensionsResetEndTimeNs = mono + time.Second.Nanoseconds() 36 | profiler.AvailableNs = 3 * time.Second.Nanoseconds() 37 | profiler.NumAgentsRegisteredForShutdown = 0 38 | extensionsResetMs, resetTimeout := profiler.CalculateExtensionsResetMs() 39 | 40 | assert.Equal(t, int64(0), extensionsResetMs) 41 | assert.Equal(t, false, resetTimeout) 42 | } 43 | 44 | func TestExtensionsResetDurationProfilerForExtensionsResetWithinDeadline(t *testing.T) { 45 | mono := Monotime() 46 | profiler := ExtensionsResetDurationProfiler{} 47 | 48 | profiler.extensionsResetStartTimeNs = mono 49 | profiler.extensionsResetEndTimeNs = mono + time.Second.Nanoseconds() 50 | profiler.AvailableNs = 3 * time.Second.Nanoseconds() 51 | profiler.NumAgentsRegisteredForShutdown = 1 52 | extensionsResetMs, resetTimeout := profiler.CalculateExtensionsResetMs() 53 | 54 | assert.Equal(t, time.Second.Milliseconds(), extensionsResetMs) 55 | assert.Equal(t, false, resetTimeout) 56 | } 57 | 58 | func TestExtensionsResetDurationProfilerForExtensionsResetTimeout(t *testing.T) { 59 | mono := Monotime() 60 | profiler := ExtensionsResetDurationProfiler{} 61 | 62 | profiler.extensionsResetStartTimeNs = mono 63 | profiler.extensionsResetEndTimeNs = mono + 3*time.Second.Nanoseconds() 64 | profiler.AvailableNs = time.Second.Nanoseconds() 65 | profiler.NumAgentsRegisteredForShutdown = 1 66 | extensionsResetMs, resetTimeout := profiler.CalculateExtensionsResetMs() 67 | 68 | assert.Equal(t, time.Second.Milliseconds(), extensionsResetMs) 69 | assert.Equal(t, true, resetTimeout) 70 | } 71 | 72 | func TestExtensionsResetDurationProfilerEndToEnd(t *testing.T) { 73 | profiler := ExtensionsResetDurationProfiler{} 74 | 75 | profiler.Start() 76 | time.Sleep(time.Second) 77 | profiler.Stop() 78 | 79 | profiler.AvailableNs = 2 * time.Second.Nanoseconds() 80 | profiler.NumAgentsRegisteredForShutdown = 1 81 | extensionsResetMs, _ := profiler.CalculateExtensionsResetMs() 82 | 83 | assert.GreaterOrEqual(t, 2*time.Second.Milliseconds(), extensionsResetMs) 84 | assert.LessOrEqual(t, time.Second.Milliseconds(), extensionsResetMs) 85 | } 86 | -------------------------------------------------------------------------------- /lambda/rapi/handler/agentexiterror.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/google/uuid" 10 | "go.amzn.com/lambda/appctx" 11 | "go.amzn.com/lambda/core" 12 | "go.amzn.com/lambda/fatalerror" 13 | "go.amzn.com/lambda/rapi/rendering" 14 | 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | type agentExitErrorHandler struct { 19 | registrationService core.RegistrationService 20 | } 21 | 22 | func (h *agentExitErrorHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 23 | agentID, ok := request.Context().Value(AgentIDCtxKey).(uuid.UUID) 24 | if !ok { 25 | rendering.RenderInternalServerError(writer, request) 26 | return 27 | } 28 | 29 | var errorType string 30 | if errorType = request.Header.Get(LambdaAgentFunctionErrorType); errorType == "" { 31 | log.Warnf("Invalid /extension/exit/error: missing %s header, agentID: %s", LambdaAgentFunctionErrorType, agentID) 32 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentMissingHeader, "%s not found", LambdaAgentFunctionErrorType) 33 | return 34 | } 35 | 36 | if externalAgent, found := h.registrationService.FindExternalAgentByID(agentID); found { 37 | if err := externalAgent.ExitError(errorType); err != nil { 38 | log.Warnf("Failed to transition agent %s to ExitError state: %s, current state %s", externalAgent.String(), err, externalAgent.GetState().Name()) 39 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentInvalidState, StateTransitionFailedForExtensionMessageFormat, 40 | externalAgent.GetState().Name(), core.AgentExitedStateName, agentID.String(), err) 41 | return 42 | } 43 | } else if internalAgent, found := h.registrationService.FindInternalAgentByID(agentID); found { 44 | if err := internalAgent.ExitError(errorType); err != nil { 45 | log.Warnf("Failed to transition agent %s to ExitError state: %s, current state %s", internalAgent.String(), err, internalAgent.GetState().Name()) 46 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentInvalidState, StateTransitionFailedForExtensionMessageFormat, 47 | internalAgent.GetState().Name(), core.AgentExitedStateName, agentID.String(), err) 48 | return 49 | } 50 | } else { 51 | log.Warnf("Unknown agent %s tried to call /extension/exit/error", agentID) 52 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentIdentifierUnknown, "Unknown "+LambdaAgentIdentifier) 53 | return 54 | } 55 | 56 | appctx.StoreFirstFatalError(appctx.FromRequest(request), fatalerror.AgentExitError) 57 | rendering.RenderAccepted(writer, request) 58 | } 59 | 60 | // NewAgentExitErrorHandler returns a new instance of http handler for serving /extension/exit/error 61 | func NewAgentExitErrorHandler(registrationService core.RegistrationService) http.Handler { 62 | return &agentExitErrorHandler{ 63 | registrationService: registrationService, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lambda/rapi/handler/agentiniterror.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/google/uuid" 10 | "go.amzn.com/lambda/appctx" 11 | "go.amzn.com/lambda/core" 12 | "go.amzn.com/lambda/fatalerror" 13 | "go.amzn.com/lambda/rapi/rendering" 14 | 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | type agentInitErrorHandler struct { 19 | registrationService core.RegistrationService 20 | } 21 | 22 | func (h *agentInitErrorHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 23 | agentID, ok := request.Context().Value(AgentIDCtxKey).(uuid.UUID) 24 | if !ok { 25 | rendering.RenderInternalServerError(writer, request) 26 | return 27 | } 28 | 29 | var errorType string 30 | if errorType = request.Header.Get(LambdaAgentFunctionErrorType); errorType == "" { 31 | log.Warnf("Invalid /extension/init/error: missing %s header, agentID: %s", LambdaAgentFunctionErrorType, agentID) 32 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentMissingHeader, "%s not found", LambdaAgentFunctionErrorType) 33 | return 34 | } 35 | 36 | if externalAgent, found := h.registrationService.FindExternalAgentByID(agentID); found { 37 | if err := externalAgent.InitError(errorType); err != nil { 38 | log.Warnf("InitError() failed for %s: %s, state is %s", externalAgent.String(), err, externalAgent.GetState().Name()) 39 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentInvalidState, StateTransitionFailedForExtensionMessageFormat, 40 | externalAgent.GetState().Name(), core.AgentInitErrorStateName, agentID.String(), err) 41 | return 42 | } 43 | } else if internalAgent, found := h.registrationService.FindInternalAgentByID(agentID); found { 44 | if err := internalAgent.InitError(errorType); err != nil { 45 | log.Warnf("InitError() failed for %s: %s, state is %s", internalAgent.String(), err, internalAgent.GetState().Name()) 46 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentInvalidState, StateTransitionFailedForExtensionMessageFormat, 47 | internalAgent.GetState().Name(), core.AgentInitErrorStateName, agentID.String(), err) 48 | return 49 | } 50 | } else { 51 | log.Warnf("Unknown agent %s tried to call /extension/init/error", LambdaAgentIdentifier) 52 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentIdentifierUnknown, "Unknown "+LambdaAgentIdentifier) 53 | return 54 | } 55 | 56 | appctx.StoreFirstFatalError(appctx.FromRequest(request), fatalerror.AgentInitError) 57 | rendering.RenderAccepted(writer, request) 58 | } 59 | 60 | // NewAgentInitErrorHandler returns a new instance of http handler for serving /extension/init/error 61 | func NewAgentInitErrorHandler(registrationService core.RegistrationService) http.Handler { 62 | return &agentInitErrorHandler{ 63 | registrationService: registrationService, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lambda/rapi/handler/agentnext.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/google/uuid" 10 | log "github.com/sirupsen/logrus" 11 | "go.amzn.com/lambda/core" 12 | "go.amzn.com/lambda/rapi/rendering" 13 | ) 14 | 15 | // A CtxKey type is used as a key for storing values in the request context. 16 | type CtxKey int 17 | 18 | // AgentIDCtxKey is the context key for fetching agent's UUID 19 | const ( 20 | AgentIDCtxKey CtxKey = iota 21 | ) 22 | 23 | type agentNextHandler struct { 24 | registrationService core.RegistrationService 25 | renderingService *rendering.EventRenderingService 26 | } 27 | 28 | func (h *agentNextHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 29 | agentID, ok := request.Context().Value(AgentIDCtxKey).(uuid.UUID) 30 | if !ok { 31 | rendering.RenderInternalServerError(writer, request) 32 | return 33 | } 34 | 35 | if externalAgent, found := h.registrationService.FindExternalAgentByID(agentID); found { 36 | if err := externalAgent.Ready(); err != nil { 37 | log.Warnf("Ready() failed for %s: %s, state is %s", externalAgent.String(), err, externalAgent.GetState().Name()) 38 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentInvalidState, StateTransitionFailedForExtensionMessageFormat, 39 | externalAgent.GetState().Name(), core.AgentReadyStateName, agentID.String(), err) 40 | return 41 | } 42 | } else if internalAgent, found := h.registrationService.FindInternalAgentByID(agentID); found { 43 | if err := internalAgent.Ready(); err != nil { 44 | log.Warnf("Ready() failed for %s: %s, state is %s", internalAgent.String(), err, internalAgent.GetState().Name()) 45 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentInvalidState, StateTransitionFailedForExtensionMessageFormat, 46 | internalAgent.GetState().Name(), core.AgentReadyStateName, agentID.String(), err) 47 | return 48 | } 49 | } else { 50 | log.Warnf("Unknown agent %s tried to call /next", agentID.String()) 51 | rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentIdentifierUnknown, "Unknown extension %s", agentID.String()) 52 | return 53 | } 54 | 55 | if err := h.renderingService.RenderAgentEvent(writer, request); err != nil { 56 | log.Error(err) 57 | rendering.RenderInternalServerError(writer, request) 58 | return 59 | } 60 | } 61 | 62 | // NewAgentNextHandler returns a new instance of http handler for serving /extension/event/next 63 | func NewAgentNextHandler(registrationService core.RegistrationService, renderingService *rendering.EventRenderingService) http.Handler { 64 | return &agentNextHandler{ 65 | registrationService: registrationService, 66 | renderingService: renderingService, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lambda/rapi/handler/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | const ( 7 | // LambdaAgentIdentifier is the header key for passing agent's id 8 | LambdaAgentIdentifier string = "Lambda-Extension-Identifier" 9 | LambdaAgentFunctionErrorType string = "Lambda-Extension-Function-Error-Type" 10 | // LambdaAgentName is agent name, provided by user (internal agents) or equal to executable basename (external agents) 11 | LambdaAgentName string = "Lambda-Extension-Name" 12 | 13 | ErrAgentIdentifierMissing string = "Extension.MissingExtensionIdentifier" 14 | ErrAgentIdentifierInvalid string = "Extension.InvalidExtensionIdentifier" 15 | 16 | errAgentNameInvalid string = "Extension.InvalidExtensionName" 17 | errAgentRegistrationClosed string = "Extension.RegistrationClosed" 18 | errAgentIdentifierUnknown string = "Extension.UnknownExtensionIdentifier" 19 | errAgentInvalidState string = "Extension.InvalidExtensionState" 20 | errAgentMissingHeader string = "Extension.MissingHeader" 21 | errTooManyExtensions string = "Extension.TooManyExtensions" 22 | errInvalidEventType string = "Extension.InvalidEventType" 23 | errInvalidRequestFormat string = "InvalidRequestFormat" 24 | 25 | StateTransitionFailedForExtensionMessageFormat string = "State transition from %s to %s failed for extension %s. Error: %s" 26 | StateTransitionFailedForRuntimeMessageFormat string = "State transition from %s to %s failed for runtime. Error: %s" 27 | ) 28 | -------------------------------------------------------------------------------- /lambda/rapi/handler/credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | 11 | log "github.com/sirupsen/logrus" 12 | 13 | "go.amzn.com/lambda/core" 14 | ) 15 | 16 | type credentialsHandler struct { 17 | credentialsService core.CredentialsService 18 | } 19 | 20 | func (h *credentialsHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 21 | token := request.Header.Get("Authorization") 22 | 23 | credentials, err := h.credentialsService.GetCredentials(token) 24 | 25 | if err != nil { 26 | errorMsg := "cannot get credentials for the provided token" 27 | log.WithError(err).Error(errorMsg) 28 | http.Error(writer, errorMsg, http.StatusNotFound) 29 | return 30 | } 31 | 32 | jsonResponse, _ := json.Marshal(*credentials) 33 | fmt.Fprint(writer, string(jsonResponse)) 34 | } 35 | 36 | func NewCredentialsHandler(credentialsService core.CredentialsService) http.Handler { 37 | return &credentialsHandler{ 38 | credentialsService: credentialsService, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lambda/rapi/handler/credentials_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "encoding/json" 8 | "log" 9 | "net/http" 10 | "net/http/httptest" 11 | "testing" 12 | "time" 13 | 14 | "github.com/stretchr/testify/assert" 15 | "go.amzn.com/lambda/appctx" 16 | "go.amzn.com/lambda/testdata" 17 | ) 18 | 19 | const InitCachingToken = "sampleInitCachingToken" 20 | const InitCachingAwsKey = "sampleAwsKey" 21 | const InitCachingAwsSecret = "sampleAwsSecret" 22 | const InitCachingAwsSessionToken = "sampleAwsSessionToken" 23 | 24 | func getRequestContext() (http.Handler, *http.Request, *httptest.ResponseRecorder) { 25 | flowTest := testdata.NewFlowTest() 26 | 27 | flowTest.ConfigureForInitCaching(InitCachingToken, InitCachingAwsKey, InitCachingAwsSecret, InitCachingAwsSessionToken) 28 | 29 | handler := NewCredentialsHandler(flowTest.CredentialsService) 30 | responseRecorder := httptest.NewRecorder() 31 | appCtx := flowTest.AppCtx 32 | 33 | request := appctx.RequestWithAppCtx(httptest.NewRequest("", "/", nil), appCtx) 34 | 35 | return handler, request, responseRecorder 36 | } 37 | 38 | func TestEmptyAuthorizationHeader(t *testing.T) { 39 | handler, request, responseRecorder := getRequestContext() 40 | 41 | handler.ServeHTTP(responseRecorder, request) 42 | assert.Equal(t, http.StatusNotFound, responseRecorder.Code) 43 | } 44 | 45 | func TestArbitraryAuthorizationHeader(t *testing.T) { 46 | handler, request, responseRecorder := getRequestContext() 47 | request.Header.Set("Authorization", "randomAuthToken") 48 | 49 | handler.ServeHTTP(responseRecorder, request) 50 | assert.Equal(t, http.StatusNotFound, responseRecorder.Code) 51 | } 52 | 53 | func TestSuccessfulGet(t *testing.T) { 54 | handler, request, responseRecorder := getRequestContext() 55 | request.Header.Set("Authorization", InitCachingToken) 56 | 57 | handler.ServeHTTP(responseRecorder, request) 58 | 59 | var responseMap map[string]string 60 | json.Unmarshal(responseRecorder.Body.Bytes(), &responseMap) 61 | assert.Equal(t, InitCachingAwsKey, responseMap["AccessKeyId"]) 62 | assert.Equal(t, InitCachingAwsSecret, responseMap["SecretAccessKey"]) 63 | assert.Equal(t, InitCachingAwsSessionToken, responseMap["Token"]) 64 | 65 | expirationTime, err := time.Parse(time.RFC3339, responseMap["Expiration"]) 66 | assert.NoError(t, err) 67 | durationUntilExpiration := time.Until(expirationTime) 68 | assert.True(t, durationUntilExpiration.Minutes() <= 30 && durationUntilExpiration.Minutes() > 29 && durationUntilExpiration.Hours() < 1) 69 | log.Println(responseRecorder.Body.String()) 70 | } 71 | -------------------------------------------------------------------------------- /lambda/rapi/handler/initerror.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "encoding/json" 8 | "io" 9 | "net/http" 10 | 11 | "go.amzn.com/lambda/appctx" 12 | "go.amzn.com/lambda/fatalerror" 13 | "go.amzn.com/lambda/interop" 14 | 15 | "go.amzn.com/lambda/core" 16 | "go.amzn.com/lambda/rapi/rendering" 17 | 18 | log "github.com/sirupsen/logrus" 19 | ) 20 | 21 | type initErrorHandler struct { 22 | registrationService core.RegistrationService 23 | } 24 | 25 | func (h *initErrorHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 26 | appCtx := appctx.FromRequest(request) 27 | interopServer := appctx.LoadInteropServer(appCtx) 28 | if interopServer == nil { 29 | log.Panic("Invalid state, cannot access interop server") 30 | } 31 | 32 | errorType := fatalerror.GetValidRuntimeOrFunctionErrorType(request.Header.Get("Lambda-Runtime-Function-Error-Type")) 33 | fnError := interop.FunctionError{Type: errorType} 34 | errorBody, err := io.ReadAll(request.Body) 35 | if err != nil { 36 | log.WithError(err).Warn("Failed to read error body") 37 | } 38 | headers := interop.InvokeResponseHeaders{ContentType: determineJSONContentType(errorBody)} 39 | response := &interop.ErrorInvokeResponse{Headers: headers, FunctionError: fnError, Payload: errorBody} 40 | 41 | runtime := h.registrationService.GetRuntime() 42 | 43 | // remove once Languages team change the endpoint to /restore/error 44 | // when an exception is throw while executing the restore hooks 45 | if runtime.GetState() == runtime.RuntimeRestoringState { 46 | if err := runtime.RestoreError(fnError); err != nil { 47 | log.Warn(err) 48 | rendering.RenderForbiddenWithTypeMsg(writer, request, rendering.ErrorTypeInvalidStateTransition, StateTransitionFailedForRuntimeMessageFormat, 49 | runtime.GetState().Name(), core.RuntimeRestoreErrorStateName, err) 50 | return 51 | } 52 | 53 | appctx.StoreInvokeErrorTraceData(appCtx, &interop.InvokeErrorTraceData{}) 54 | rendering.RenderAccepted(writer, request) 55 | return 56 | } 57 | 58 | if err := runtime.InitError(); err != nil { 59 | log.Warn(err) 60 | rendering.RenderForbiddenWithTypeMsg(writer, request, rendering.ErrorTypeInvalidStateTransition, StateTransitionFailedForRuntimeMessageFormat, 61 | runtime.GetState().Name(), core.RuntimeInitErrorStateName, err) 62 | return 63 | } 64 | 65 | if err := interopServer.SendInitErrorResponse(response); err != nil { 66 | rendering.RenderInteropError(writer, request, err) 67 | return 68 | } 69 | 70 | appctx.StoreInvokeErrorTraceData(appCtx, &interop.InvokeErrorTraceData{}) 71 | rendering.RenderAccepted(writer, request) 72 | } 73 | 74 | // NewInitErrorHandler returns a new instance of http handler 75 | // for serving /runtime/init/error. 76 | func NewInitErrorHandler(registrationService core.RegistrationService) http.Handler { 77 | return &initErrorHandler{registrationService: registrationService} 78 | } 79 | 80 | func determineJSONContentType(body []byte) string { 81 | if json.Valid(body) { 82 | return "application/json" 83 | } 84 | return "application/octet-stream" 85 | } 86 | -------------------------------------------------------------------------------- /lambda/rapi/handler/initerror_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "net/http" 10 | "net/http/httptest" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/require" 14 | "go.amzn.com/lambda/appctx" 15 | "go.amzn.com/lambda/fatalerror" 16 | "go.amzn.com/lambda/testdata" 17 | ) 18 | 19 | // TestInitErrorHandler tests that API handler for 20 | // initialization-time errors receives and passes 21 | // information through to the Slicer unmodified. 22 | func TestInitErrorHandler(t *testing.T) { 23 | t.Run("GA", func(t *testing.T) { runTestInitErrorHandler(t) }) 24 | } 25 | 26 | func runTestInitErrorHandler(t *testing.T) { 27 | flowTest := testdata.NewFlowTest() 28 | flowTest.ConfigureForInit() 29 | 30 | handler := NewInitErrorHandler(flowTest.RegistrationService) 31 | responseRecorder := httptest.NewRecorder() 32 | appCtx := flowTest.AppCtx 33 | 34 | // Error request, as submitted by custom runtime. 35 | errorBody := []byte("My byte array is yours") 36 | errorType := "ErrorType" 37 | errorContentType := "application/MyBinaryType" 38 | 39 | request := appctx.RequestWithAppCtx(httptest.NewRequest("POST", "/", bytes.NewReader(errorBody)), appCtx) 40 | request.Header.Set("Content-Type", errorContentType) 41 | request.Header.Set("Lambda-runtime-functioN-erroR-typE", errorType) // Headers are case-insensitive anyway ! 42 | 43 | // Submit ! 44 | handler.ServeHTTP(responseRecorder, request) 45 | 46 | // Assertions 47 | 48 | // Validate response sent to the runtime. 49 | require.Equal(t, http.StatusAccepted, responseRecorder.Code, "Handler returned wrong status code: got %v expected %v", 50 | responseRecorder.Code, http.StatusAccepted) 51 | require.JSONEq(t, fmt.Sprintf("{\"status\":\"%s\"}\n", "OK"), responseRecorder.Body.String()) 52 | require.Equal(t, "application/json", responseRecorder.Header().Get("Content-Type")) 53 | 54 | // Validate init error persisted in the application context. 55 | errorResponse := flowTest.InteropServer.ErrorResponse 56 | require.NotNil(t, errorResponse) 57 | require.Nil(t, flowTest.InteropServer.Response) 58 | 59 | // Slicer falls back to using ErrorMessage when error 60 | // payload is not provided. This fallback is not part 61 | // of the RAPID API spec and is not available to 62 | // customers. 63 | require.Equal(t, "", errorResponse.FunctionError.Message) 64 | 65 | // Slicer falls back to using ErrorType when error 66 | // payload is not provided. Customers can set error 67 | // type via header to use this fallback. 68 | require.Equal(t, fatalerror.RuntimeUnknown, errorResponse.FunctionError.Type) 69 | 70 | // Payload is arbitrary data that customers submit - it's error response body. 71 | require.Equal(t, errorBody, errorResponse.Payload) 72 | } 73 | -------------------------------------------------------------------------------- /lambda/rapi/handler/invocationnext.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | "go.amzn.com/lambda/core" 10 | "go.amzn.com/lambda/rapi/rendering" 11 | 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | type invocationNextHandler struct { 16 | registrationService core.RegistrationService 17 | renderingService *rendering.EventRenderingService 18 | } 19 | 20 | func (h *invocationNextHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 21 | runtime := h.registrationService.GetRuntime() 22 | err := runtime.Ready() 23 | if err != nil { 24 | log.Warn(err) 25 | rendering.RenderForbiddenWithTypeMsg(writer, request, rendering.ErrorTypeInvalidStateTransition, StateTransitionFailedForRuntimeMessageFormat, 26 | runtime.GetState().Name(), core.RuntimeReadyStateName, err) 27 | return 28 | } 29 | err = h.renderingService.RenderRuntimeEvent(writer, request) 30 | if err != nil { 31 | log.Error(err) 32 | rendering.RenderInternalServerError(writer, request) 33 | return 34 | } 35 | } 36 | 37 | // NewInvocationNextHandler returns a new instance of http handler 38 | // for serving /runtime/invocation/next. 39 | func NewInvocationNextHandler(registrationService core.RegistrationService, renderingService *rendering.EventRenderingService) http.Handler { 40 | return &invocationNextHandler{ 41 | registrationService: registrationService, 42 | renderingService: renderingService, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lambda/rapi/handler/invocationresponse.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | "go.amzn.com/lambda/appctx" 10 | "go.amzn.com/lambda/core" 11 | "go.amzn.com/lambda/fatalerror" 12 | "go.amzn.com/lambda/interop" 13 | "go.amzn.com/lambda/rapi/rendering" 14 | 15 | "github.com/go-chi/chi" 16 | log "github.com/sirupsen/logrus" 17 | ) 18 | 19 | const ( 20 | StreamingFunctionResponseMode = "streaming" 21 | ) 22 | 23 | type invocationResponseHandler struct { 24 | registrationService core.RegistrationService 25 | } 26 | 27 | func (h *invocationResponseHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 28 | appCtx := appctx.FromRequest(request) 29 | 30 | server := appctx.LoadResponseSender(appCtx) 31 | if server == nil { 32 | log.Panic("Invalid state, cannot access interop server") 33 | } 34 | 35 | runtime := h.registrationService.GetRuntime() 36 | if err := runtime.InvocationResponse(); err != nil { 37 | log.Warn(err) 38 | rendering.RenderForbiddenWithTypeMsg(writer, request, rendering.ErrorTypeInvalidStateTransition, StateTransitionFailedForRuntimeMessageFormat, 39 | runtime.GetState().Name(), core.RuntimeInvocationResponseStateName, err) 40 | return 41 | } 42 | 43 | invokeID := chi.URLParam(request, "awsrequestid") 44 | 45 | headers := map[string]string{contentTypeHeader: request.Header.Get(contentTypeHeader)} 46 | if functionResponseMode := request.Header.Get(functionResponseModeHeader); functionResponseMode != "" { 47 | switch functionResponseMode { 48 | case StreamingFunctionResponseMode: 49 | headers[functionResponseModeHeader] = functionResponseMode 50 | default: 51 | errHeaders := interop.InvokeResponseHeaders{ 52 | ContentType: request.Header.Get(contentTypeHeader), 53 | } 54 | fnError := interop.FunctionError{Type: fatalerror.RuntimeInvalidResponseModeHeader} 55 | response := &interop.ErrorInvokeResponse{ 56 | Headers: errHeaders, 57 | FunctionError: fnError, 58 | Payload: []byte{}, 59 | } 60 | 61 | _ = server.SendErrorResponse(chi.URLParam(request, "awsrequestid"), response) 62 | rendering.RenderInvalidFunctionResponseMode(writer, request) 63 | return 64 | } 65 | } 66 | 67 | response := &interop.StreamableInvokeResponse{ 68 | Headers: headers, 69 | Payload: request.Body, 70 | Trailers: request.Trailer, 71 | Request: &interop.CancellableRequest{Request: request}, 72 | } 73 | 74 | if err := server.SendResponse(invokeID, response); err != nil { 75 | switch err := err.(type) { 76 | case *interop.ErrorResponseTooLarge: 77 | if server.SendErrorResponse(invokeID, err.AsErrorResponse()) != nil { 78 | rendering.RenderInteropError(writer, request, err) 79 | return 80 | } 81 | 82 | appctx.StoreInvokeErrorTraceData(appCtx, &interop.InvokeErrorTraceData{}) 83 | 84 | if err := runtime.ResponseSent(); err != nil { 85 | log.Panic(err) 86 | } 87 | 88 | rendering.RenderRequestEntityTooLarge(writer, request) 89 | return 90 | 91 | case *interop.ErrorResponseTooLargeDI: 92 | // in DirectInvoke case, the (truncated) response is already sent back to the caller 93 | if err := runtime.ResponseSent(); err != nil { 94 | log.Panic(err) 95 | } 96 | 97 | rendering.RenderRequestEntityTooLarge(writer, request) 98 | return 99 | 100 | case *interop.ErrTruncatedResponse: 101 | if err := runtime.ResponseSent(); err != nil { 102 | log.Panic(err) 103 | } 104 | 105 | rendering.RenderTruncatedHTTPRequestError(writer, request) 106 | return 107 | 108 | case *interop.ErrInternalPlatformError: 109 | rendering.RenderInternalServerError(writer, request) 110 | return 111 | 112 | default: 113 | rendering.RenderInteropError(writer, request, err) 114 | return 115 | } 116 | } 117 | 118 | if err := runtime.ResponseSent(); err != nil { 119 | log.Panic(err) 120 | } 121 | 122 | rendering.RenderAccepted(writer, request) 123 | } 124 | 125 | // NewInvocationResponseHandler returns a new instance of http handler 126 | // for serving /runtime/invocation/{awsrequestid}/response. 127 | func NewInvocationResponseHandler(registrationService core.RegistrationService) http.Handler { 128 | return &invocationResponseHandler{ 129 | registrationService: registrationService, 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lambda/rapi/handler/mime_type_error_cause_json.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | 10 | "go.amzn.com/lambda/rapi/model" 11 | 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | // Validation, serialization & deserialization for 16 | // MIME type: application/vnd.aws.lambda.error.cause+json" 17 | type errorWithCauseRequest struct { 18 | ErrorMessage string `json:"errorMessage"` 19 | ErrorType string `json:"errorType"` 20 | StackTrace []string `json:"stackTrace"` 21 | ErrorCause json.RawMessage `json:"errorCause"` 22 | } 23 | 24 | func newErrorWithCauseRequest(requestBody []byte) (*errorWithCauseRequest, error) { 25 | var parsedRequest errorWithCauseRequest 26 | if err := json.Unmarshal(requestBody, &parsedRequest); err != nil { 27 | return nil, fmt.Errorf("error unmarshalling request body with error cause: %s", err) 28 | } 29 | 30 | return &parsedRequest, nil 31 | } 32 | 33 | func (r *errorWithCauseRequest) getInvokeErrorResponse() ([]byte, error) { 34 | responseBody := model.ErrorResponse{ 35 | ErrorMessage: r.ErrorMessage, 36 | ErrorType: r.ErrorType, 37 | StackTrace: r.StackTrace, 38 | } 39 | 40 | filteredResponseBody, err := json.Marshal(responseBody) 41 | if err != nil { 42 | return nil, fmt.Errorf("error marshalling invocation/error response body: %s", err) 43 | } 44 | 45 | return filteredResponseBody, nil 46 | } 47 | 48 | func (r *errorWithCauseRequest) getValidatedXRayCause() json.RawMessage { 49 | if len(r.ErrorCause) == 0 { 50 | return nil 51 | } 52 | 53 | validErrorCauseJSON, err := model.ValidatedErrorCauseJSON(r.ErrorCause) 54 | if err != nil { 55 | log.WithError(err).Errorf("errorCause validation error, Content-Type: %s", errorWithCauseContentType) 56 | return nil 57 | } 58 | 59 | return validErrorCauseJSON 60 | } 61 | -------------------------------------------------------------------------------- /lambda/rapi/handler/ping.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | type pingHandler struct { 13 | // 14 | } 15 | 16 | func (h *pingHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 17 | if _, err := writer.Write([]byte("pong")); err != nil { 18 | log.WithError(err).Fatal("Failed to write 'pong' response") 19 | } 20 | } 21 | 22 | // NewPingHandler returns a new instance of http handler 23 | // for serving /ping. 24 | func NewPingHandler() http.Handler { 25 | return &pingHandler{} 26 | } 27 | -------------------------------------------------------------------------------- /lambda/rapi/handler/restoreerror.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "go.amzn.com/lambda/appctx" 11 | "go.amzn.com/lambda/core" 12 | "go.amzn.com/lambda/fatalerror" 13 | "go.amzn.com/lambda/interop" 14 | "go.amzn.com/lambda/rapi/rendering" 15 | ) 16 | 17 | type restoreErrorHandler struct { 18 | registrationService core.RegistrationService 19 | } 20 | 21 | func (h *restoreErrorHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 22 | appCtx := appctx.FromRequest(request) 23 | server := appctx.LoadInteropServer(appCtx) 24 | if server == nil { 25 | log.Panic("Invalid state, cannot access interop server") 26 | } 27 | 28 | errorType := fatalerror.GetValidRuntimeOrFunctionErrorType(request.Header.Get("Lambda-Runtime-Function-Error-Type")) 29 | fnError := interop.FunctionError{Type: errorType} 30 | 31 | runtime := h.registrationService.GetRuntime() 32 | 33 | if err := runtime.RestoreError(fnError); err != nil { 34 | log.Warn(err) 35 | rendering.RenderForbiddenWithTypeMsg(writer, request, rendering.ErrorTypeInvalidStateTransition, StateTransitionFailedForRuntimeMessageFormat, 36 | runtime.GetState().Name(), core.RuntimeRestoreErrorStateName, err) 37 | return 38 | } 39 | 40 | appctx.StoreInvokeErrorTraceData(appCtx, &interop.InvokeErrorTraceData{}) 41 | 42 | rendering.RenderAccepted(writer, request) 43 | } 44 | 45 | func NewRestoreErrorHandler(registrationService core.RegistrationService) http.Handler { 46 | return &restoreErrorHandler{registrationService: registrationService} 47 | } 48 | -------------------------------------------------------------------------------- /lambda/rapi/handler/restoreerror_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "net/http" 10 | "net/http/httptest" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/require" 14 | "go.amzn.com/lambda/appctx" 15 | "go.amzn.com/lambda/testdata" 16 | ) 17 | 18 | func TestRestoreErrorHandler(t *testing.T) { 19 | t.Run("GA", func(t *testing.T) { runTestRestoreErrorHandler(t) }) 20 | } 21 | 22 | func runTestRestoreErrorHandler(t *testing.T) { 23 | flowTest := testdata.NewFlowTest() 24 | flowTest.ConfigureForRestoring() 25 | 26 | handler := NewRestoreErrorHandler(flowTest.RegistrationService) 27 | responseRecorder := httptest.NewRecorder() 28 | appCtx := flowTest.AppCtx 29 | 30 | errorBody := []byte("My byte array is yours") 31 | errorType := "ErrorType" 32 | errorContentType := "application/MyBinaryType" 33 | 34 | request := appctx.RequestWithAppCtx(httptest.NewRequest("POST", "/", bytes.NewReader(errorBody)), appCtx) 35 | 36 | request.Header.Set("Content-Type", errorContentType) 37 | request.Header.Set("Lambda-Runtime-Function-Error-Type", errorType) 38 | 39 | handler.ServeHTTP(responseRecorder, request) 40 | 41 | require.Equal(t, http.StatusAccepted, responseRecorder.Code, "Handler returned wrong status code: got %v expected %v", responseRecorder.Code, http.StatusAccepted) 42 | require.JSONEq(t, fmt.Sprintf("{\"status\":\"%s\"}\n", "OK"), responseRecorder.Body.String()) 43 | require.Equal(t, "application/json", responseRecorder.Header().Get("Content-Type")) 44 | } 45 | -------------------------------------------------------------------------------- /lambda/rapi/handler/restorenext.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "go.amzn.com/lambda/core" 11 | "go.amzn.com/lambda/rapi/rendering" 12 | ) 13 | 14 | type restoreNextHandler struct { 15 | registrationService core.RegistrationService 16 | renderingService *rendering.EventRenderingService 17 | } 18 | 19 | func (h *restoreNextHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 20 | runtime := h.registrationService.GetRuntime() 21 | err := runtime.RestoreReady() 22 | if err != nil { 23 | log.Warn(err) 24 | rendering.RenderForbiddenWithTypeMsg(writer, request, rendering.ErrorTypeInvalidStateTransition, StateTransitionFailedForRuntimeMessageFormat, runtime.GetState().Name(), core.RuntimeReadyStateName, err) 25 | return 26 | } 27 | err = h.renderingService.RenderRuntimeEvent(writer, request) 28 | if err != nil { 29 | log.Error(err) 30 | rendering.RenderInternalServerError(writer, request) 31 | return 32 | } 33 | } 34 | 35 | func NewRestoreNextHandler(registrationService core.RegistrationService, renderingService *rendering.EventRenderingService) http.Handler { 36 | return &restoreNextHandler{ 37 | registrationService: registrationService, 38 | renderingService: renderingService, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lambda/rapi/handler/restorenext_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | "net/http/httptest" 10 | "strconv" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/stretchr/testify/assert" 15 | "go.amzn.com/lambda/appctx" 16 | "go.amzn.com/lambda/interop" 17 | "go.amzn.com/lambda/telemetry" 18 | "go.amzn.com/lambda/testdata" 19 | ) 20 | 21 | func TestRenderRestoreNext(t *testing.T) { 22 | flowTest := testdata.NewFlowTest() 23 | flowTest.ConfigureForInit() 24 | handler := NewRestoreNextHandler(flowTest.RegistrationService, flowTest.RenderingService) 25 | responseRecorder := httptest.NewRecorder() 26 | appCtx := flowTest.AppCtx 27 | 28 | flowTest.ConfigureForRestore() 29 | request := appctx.RequestWithAppCtx(httptest.NewRequest("", "/", nil), appCtx) 30 | handler.ServeHTTP(responseRecorder, request) 31 | 32 | assert.Equal(t, http.StatusOK, responseRecorder.Code) 33 | } 34 | 35 | func TestBrokenRenderer(t *testing.T) { 36 | flowTest := testdata.NewFlowTest() 37 | flowTest.ConfigureForInit() 38 | handler := NewRestoreNextHandler(flowTest.RegistrationService, flowTest.RenderingService) 39 | responseRecorder := httptest.NewRecorder() 40 | appCtx := flowTest.AppCtx 41 | 42 | flowTest.ConfigureForRestore() 43 | flowTest.RenderingService.SetRenderer(&mockBrokenRenderer{}) 44 | request := appctx.RequestWithAppCtx(httptest.NewRequest("", "/", nil), appCtx) 45 | handler.ServeHTTP(responseRecorder, request) 46 | 47 | assert.Equal(t, http.StatusInternalServerError, responseRecorder.Code) 48 | 49 | assert.JSONEq(t, `{"errorMessage":"Internal Server Error","errorType":"InternalServerError"}`, responseRecorder.Body.String()) 50 | } 51 | 52 | func TestRenderRestoreAfterInvoke(t *testing.T) { 53 | flowTest := testdata.NewFlowTest() 54 | flowTest.ConfigureForInit() 55 | handler := NewInvocationNextHandler(flowTest.RegistrationService, flowTest.RenderingService) 56 | responseRecorder := httptest.NewRecorder() 57 | appCtx := flowTest.AppCtx 58 | 59 | deadlineNs := 12345 60 | invokePayload := "Payload" 61 | invoke := &interop.Invoke{ 62 | TraceID: "Root=RootID;Parent=LambdaFrontend;Sampled=1", 63 | ID: "ID", 64 | InvokedFunctionArn: "InvokedFunctionArn", 65 | CognitoIdentityID: "CognitoIdentityId1", 66 | CognitoIdentityPoolID: "CognitoIdentityPoolId1", 67 | ClientContext: "ClientContext", 68 | DeadlineNs: strconv.Itoa(deadlineNs), 69 | ContentType: "image/png", 70 | Payload: strings.NewReader(invokePayload), 71 | } 72 | 73 | ctx := telemetry.NewTraceContext(context.Background(), "RootID", "InvocationSubegmentID") 74 | flowTest.ConfigureForInvoke(ctx, invoke) 75 | 76 | request := appctx.RequestWithAppCtx(httptest.NewRequest("", "/", nil), appCtx) 77 | handler.ServeHTTP(responseRecorder, request) 78 | 79 | assert.Equal(t, http.StatusOK, responseRecorder.Code) 80 | 81 | restoreHandler := NewRestoreNextHandler(flowTest.RegistrationService, flowTest.RenderingService) 82 | restoreRequest := appctx.RequestWithAppCtx(httptest.NewRequest("", "/", nil), appCtx) 83 | responseRecorder = httptest.NewRecorder() 84 | restoreHandler.ServeHTTP(responseRecorder, restoreRequest) 85 | 86 | assert.Equal(t, http.StatusForbidden, responseRecorder.Code) 87 | } 88 | -------------------------------------------------------------------------------- /lambda/rapi/handler/runtimelogs_stub.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "go.amzn.com/lambda/rapi/model" 11 | "go.amzn.com/lambda/rapi/rendering" 12 | ) 13 | 14 | const ( 15 | logsAPIDisabledErrorType = "Logs.NotSupported" 16 | telemetryAPIDisabledErrorType = "Telemetry.NotSupported" 17 | ) 18 | 19 | type runtimeLogsStubAPIHandler struct{} 20 | 21 | func (h *runtimeLogsStubAPIHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 22 | if err := rendering.RenderJSON(http.StatusAccepted, writer, request, &model.ErrorResponse{ 23 | ErrorType: logsAPIDisabledErrorType, 24 | ErrorMessage: "Logs API is not supported", 25 | }); err != nil { 26 | log.WithError(err).Warn("Error while rendering response") 27 | http.Error(writer, err.Error(), http.StatusInternalServerError) 28 | } 29 | } 30 | 31 | // NewRuntimeLogsAPIStubHandler returns a new instance of http handler 32 | // for serving /runtime/logs when a telemetry service implementation is absent 33 | func NewRuntimeLogsAPIStubHandler() http.Handler { 34 | return &runtimeLogsStubAPIHandler{} 35 | } 36 | 37 | type runtimeTelemetryAPIStubHandler struct{} 38 | 39 | func (h *runtimeTelemetryAPIStubHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 40 | if err := rendering.RenderJSON(http.StatusAccepted, writer, request, &model.ErrorResponse{ 41 | ErrorType: telemetryAPIDisabledErrorType, 42 | ErrorMessage: "Telemetry API is not supported", 43 | }); err != nil { 44 | log.WithError(err).Warn("Error while rendering response") 45 | http.Error(writer, err.Error(), http.StatusInternalServerError) 46 | } 47 | } 48 | 49 | // NewRuntimeTelemetryAPIStubHandler returns a new instance of http handler 50 | // for serving /runtime/logs when a telemetry service implementation is absent 51 | func NewRuntimeTelemetryAPIStubHandler() http.Handler { 52 | return &runtimeTelemetryAPIStubHandler{} 53 | } 54 | -------------------------------------------------------------------------------- /lambda/rapi/handler/runtimelogs_stub_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handler 5 | 6 | import ( 7 | "bytes" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestSuccessfulRuntimeLogsAPIStub202Response(t *testing.T) { 16 | handler := NewRuntimeLogsAPIStubHandler() 17 | requestBody := []byte(`foobar`) 18 | request := httptest.NewRequest("PUT", "/logs", bytes.NewBuffer(requestBody)) 19 | responseRecorder := httptest.NewRecorder() 20 | 21 | handler.ServeHTTP(responseRecorder, request) 22 | 23 | assert.Equal(t, http.StatusAccepted, responseRecorder.Code) 24 | assert.JSONEq(t, `{"errorMessage":"Logs API is not supported","errorType":"Logs.NotSupported"}`, responseRecorder.Body.String()) 25 | } 26 | 27 | func TestSuccessfulRuntimeTelemetryAPIStub202Response(t *testing.T) { 28 | handler := NewRuntimeTelemetryAPIStubHandler() 29 | requestBody := []byte(`foobar`) 30 | request := httptest.NewRequest("PUT", "/telemetry", bytes.NewBuffer(requestBody)) 31 | responseRecorder := httptest.NewRecorder() 32 | 33 | handler.ServeHTTP(responseRecorder, request) 34 | 35 | assert.Equal(t, http.StatusAccepted, responseRecorder.Code) 36 | assert.JSONEq(t, `{"errorMessage":"Telemetry API is not supported","errorType":"Telemetry.NotSupported"}`, responseRecorder.Body.String()) 37 | } 38 | -------------------------------------------------------------------------------- /lambda/rapi/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package middleware 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | 10 | "github.com/google/uuid" 11 | "go.amzn.com/lambda/extensions" 12 | "go.amzn.com/lambda/rapi/handler" 13 | "go.amzn.com/lambda/rapi/rendering" 14 | 15 | "github.com/go-chi/chi" 16 | "go.amzn.com/lambda/appctx" 17 | 18 | log "github.com/sirupsen/logrus" 19 | ) 20 | 21 | // AwsRequestIDValidator validates that {awsrequestid} parameter 22 | // is present in the URL and matches to the currently active id. 23 | func AwsRequestIDValidator(next http.Handler) http.Handler { 24 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 25 | appCtx := appctx.FromRequest(r) 26 | interopServer := appctx.LoadInteropServer(appCtx) 27 | 28 | if interopServer == nil { 29 | log.Panic("Invalid state, cannot access interop server") 30 | } 31 | 32 | invokeID := chi.URLParam(r, "awsrequestid") 33 | if invokeID == "" || invokeID != interopServer.GetCurrentInvokeID() { 34 | rendering.RenderInvalidRequestID(w, r) 35 | return 36 | } 37 | 38 | next.ServeHTTP(w, r) 39 | }) 40 | } 41 | 42 | // AgentUniqueIdentifierHeaderValidator validates that the request contains a valid agent unique identifier in the headers 43 | func AgentUniqueIdentifierHeaderValidator(next http.Handler) http.Handler { 44 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 | agentIdentifier := r.Header.Get(handler.LambdaAgentIdentifier) 46 | if len(agentIdentifier) == 0 { 47 | rendering.RenderForbiddenWithTypeMsg(w, r, handler.ErrAgentIdentifierMissing, "Missing Lambda-Extension-Identifier header") 48 | return 49 | } 50 | agentID, e := uuid.Parse(agentIdentifier) 51 | if e != nil { 52 | rendering.RenderForbiddenWithTypeMsg(w, r, handler.ErrAgentIdentifierInvalid, "Invalid Lambda-Extension-Identifier") 53 | return 54 | } 55 | 56 | r = r.WithContext(context.WithValue(r.Context(), handler.AgentIDCtxKey, agentID)) 57 | next.ServeHTTP(w, r) 58 | }) 59 | } 60 | 61 | // AppCtxMiddleware injects application context into request context. 62 | func AppCtxMiddleware(appCtx appctx.ApplicationContext) func(next http.Handler) http.Handler { 63 | return func(next http.Handler) http.Handler { 64 | fn := func(w http.ResponseWriter, r *http.Request) { 65 | r = appctx.RequestWithAppCtx(r, appCtx) 66 | next.ServeHTTP(w, r) 67 | } 68 | return http.HandlerFunc(fn) 69 | } 70 | } 71 | 72 | // AccessLogMiddleware writes api access log. 73 | func AccessLogMiddleware() func(next http.Handler) http.Handler { 74 | return func(next http.Handler) http.Handler { 75 | fn := func(w http.ResponseWriter, r *http.Request) { 76 | log.Debug("API request - ", r.Method, " ", r.URL, ", Headers:", r.Header) 77 | next.ServeHTTP(w, r) 78 | } 79 | return http.HandlerFunc(fn) 80 | } 81 | } 82 | 83 | func AllowIfExtensionsEnabled(next http.Handler) http.Handler { 84 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 85 | if !extensions.AreEnabled() { 86 | w.WriteHeader(http.StatusNotFound) 87 | return 88 | } 89 | next.ServeHTTP(w, r) 90 | }) 91 | } 92 | 93 | // RuntimeReleaseMiddleware places runtime_release into app context. 94 | func RuntimeReleaseMiddleware() func(next http.Handler) http.Handler { 95 | return func(next http.Handler) http.Handler { 96 | fn := func(w http.ResponseWriter, r *http.Request) { 97 | appCtx := appctx.FromRequest(r) 98 | // Place runtime_release into app context. 99 | appctx.UpdateAppCtxWithRuntimeRelease(r, appCtx) 100 | next.ServeHTTP(w, r) 101 | } 102 | return http.HandlerFunc(fn) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lambda/rapi/model/agentevent.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | // AgentEvent is one of INVOKE, SHUTDOWN agent events 7 | type AgentEvent struct { 8 | EventType string `json:"eventType"` 9 | DeadlineMs int64 `json:"deadlineMs"` 10 | } 11 | 12 | // AgentInvokeEvent is the response to agent's get next request 13 | type AgentInvokeEvent struct { 14 | *AgentEvent 15 | RequestID string `json:"requestId"` 16 | InvokedFunctionArn string `json:"invokedFunctionArn"` 17 | Tracing *Tracing `json:"tracing,omitempty"` 18 | } 19 | 20 | // AgentShutdownEvent is the response to agent's get next request 21 | type AgentShutdownEvent struct { 22 | *AgentEvent 23 | ShutdownReason string `json:"shutdownReason"` 24 | } 25 | -------------------------------------------------------------------------------- /lambda/rapi/model/agentregisterresponse.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | // ExtensionRegisterResponse is a response returned by the API server on extension/register post request 7 | type ExtensionRegisterResponse struct { 8 | AccountID string `json:"accountId,omitempty"` 9 | FunctionName string `json:"functionName"` 10 | FunctionVersion string `json:"functionVersion"` 11 | Handler string `json:"handler"` 12 | } 13 | -------------------------------------------------------------------------------- /lambda/rapi/model/cognitoidentity.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | // CognitoIdentity is returned by the API server in a response headers, 7 | // providing information about client's Cognito identity. 8 | type CognitoIdentity struct { 9 | CognitoIdentityID string `json:"cognitoIdentityId"` 10 | CognitoIdentityPoolID string `json:"cognitoIdentityPoolId"` 11 | } 12 | -------------------------------------------------------------------------------- /lambda/rapi/model/error_cause.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | ) 10 | 11 | // MaxErrorCauseSizeBytes limits the size of a cause, 12 | // since the max X-Ray document size of 64kB 13 | const MaxErrorCauseSizeBytes = 64 << 10 14 | 15 | type exceptionStackFrame struct { 16 | Path string `json:"path,omitempty"` 17 | Line int `json:"line,omitempty"` 18 | Label string `json:"label,omitempty"` 19 | } 20 | 21 | type exception struct { 22 | Message string `json:"message,omitempty"` 23 | Type string `json:"type,omitempty"` 24 | Stack []exceptionStackFrame `json:"stack,omitempty"` 25 | } 26 | 27 | // ErrorCause represents the cause of an error reported by 28 | // the runtime, and may contain stack traces and exceptions 29 | type ErrorCause struct { 30 | Exceptions []exception `json:"exceptions"` 31 | WorkingDir string `json:"working_directory"` 32 | Paths []string `json:"paths"` 33 | Message string `json:"message,omitempty"` 34 | } 35 | 36 | // newErrorCause unmarshals JSON into an ErrorCause 37 | func newErrorCause(errorCauseJSON []byte) (*ErrorCause, error) { 38 | var ec ErrorCause 39 | 40 | if err := json.Unmarshal(errorCauseJSON, &ec); err != nil { 41 | return nil, fmt.Errorf("failed to parse error cause JSON: %s", err) 42 | } 43 | 44 | return &ec, nil 45 | } 46 | 47 | // isValid validates the ErrorCause format 48 | func (ec *ErrorCause) isValid() bool { 49 | if len(ec.WorkingDir) == 0 && len(ec.Paths) == 0 && len(ec.Exceptions) == 0 && len(ec.Message) == 0 { 50 | // X-Ray public docs imply WorkingDir, Paths & Exceptions are not optional, 51 | // but we are less restrictive. 52 | 53 | // Message is not a valid field as per the latest X-Ray docs, but native runtimes 54 | // use it via LambdaSandboxRuntime and the X-Ray Data Plane frontend supports it. 55 | return false 56 | } 57 | 58 | return true 59 | } 60 | 61 | func (ec *ErrorCause) croppedJSON() []byte { 62 | // Iteratively crop the error cause by a factor of its size 63 | // until it is within the size limit 64 | 65 | truncationFactors := []float64{0.8, 0.6, 0.4, 0.2} 66 | for _, factor := range truncationFactors { 67 | compactor := newErrorCauseCompactor(*ec) 68 | compactor.crop(factor) 69 | validErrorCauseJSON, err := json.Marshal(compactor.cause()) 70 | if err != nil { 71 | break 72 | } 73 | 74 | if len(validErrorCauseJSON) <= MaxErrorCauseSizeBytes { 75 | return validErrorCauseJSON 76 | } 77 | } 78 | 79 | // If compaction failed, drop Exceptions & Path, and truncate 80 | // Message & WorkingDir, using smallest possible factor 81 | compactor := newErrorCauseCompactor(*ec) 82 | compactor.crop(0) 83 | 84 | validErrorCauseJSON, err := json.Marshal(compactor.cause()) 85 | if err != nil { 86 | return nil 87 | } 88 | 89 | return validErrorCauseJSON 90 | } 91 | 92 | // ValidatedErrorCauseJSON returns an error if 93 | // the ErrorCause JSON has an invalid format 94 | func ValidatedErrorCauseJSON(errorCauseJSON []byte) ([]byte, error) { 95 | errorCause, err := newErrorCause(errorCauseJSON) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | if !errorCause.isValid() { 101 | return nil, fmt.Errorf("error cause body has invalid format: %s", errorCauseJSON) 102 | } 103 | 104 | validErrorCauseJSON, err := json.Marshal(errorCause) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | if len(validErrorCauseJSON) > MaxErrorCauseSizeBytes { 110 | return errorCause.croppedJSON(), nil 111 | } 112 | 113 | return validErrorCauseJSON, nil 114 | } 115 | -------------------------------------------------------------------------------- /lambda/rapi/model/error_cause_compactor.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | import ( 7 | "math" 8 | ) 9 | 10 | const paddingForFieldNames = 4096 11 | 12 | type errorCauseCompactor struct { 13 | ec ErrorCause 14 | } 15 | 16 | func newErrorCauseCompactor(errorCause ErrorCause) *errorCauseCompactor { 17 | ec := errorCause 18 | return &errorCauseCompactor{ec} 19 | } 20 | 21 | // cropStackTraces crops Exceptions & Paths of an error cause 22 | // by a factor in [0,1]. e.g. 0 removes all elements, 1 removes 23 | // nothing, 0.5 removes half the array elements 24 | func (c *errorCauseCompactor) cropStackTraces(factor float64) { 25 | if factor > 0 { 26 | factor = math.Min(factor, 1) // number in (0,1] 27 | exceptionsLen := float64(len(c.ec.Exceptions)) * factor 28 | pathLen := float64(len(c.ec.Paths)) * factor 29 | 30 | c.ec.Exceptions = c.ec.Exceptions[:int(exceptionsLen)] 31 | c.ec.Paths = c.ec.Paths[:int(pathLen)] 32 | 33 | return 34 | } 35 | 36 | c.ec.Exceptions = nil 37 | c.ec.Paths = nil 38 | } 39 | 40 | // cropMessage crops Message of an error cause to be half the 41 | // max size when the factor is 0 (worst-case) 42 | func (c *errorCauseCompactor) cropMessage(factor float64) { 43 | if factor > 0 { 44 | return 45 | } 46 | 47 | length := ((MaxErrorCauseSizeBytes - paddingForFieldNames) / 2) 48 | c.ec.Message = cropString(c.ec.Message, length) 49 | } 50 | 51 | // cropWorkingDir crops WorkingDir of an error cause to be half 52 | // the max size when the factor is 0 (worst-case) 53 | func (c *errorCauseCompactor) cropWorkingDir(factor float64) { 54 | if factor > 0 { 55 | return 56 | } 57 | 58 | length := ((MaxErrorCauseSizeBytes - paddingForFieldNames) / 2) 59 | c.ec.WorkingDir = cropString(c.ec.WorkingDir, length) 60 | } 61 | 62 | func (c *errorCauseCompactor) crop(factor float64) { 63 | c.cropStackTraces(factor) 64 | c.cropMessage(factor) 65 | c.cropWorkingDir(factor) 66 | } 67 | 68 | func (c *errorCauseCompactor) cause() *ErrorCause { 69 | return &c.ec 70 | } 71 | 72 | func cropString(str string, length int) string { 73 | if len(str) <= length { 74 | return str 75 | } 76 | 77 | truncationIndicator := `...` 78 | length = length - len(truncationIndicator) 79 | return str[:length] + truncationIndicator 80 | } 81 | -------------------------------------------------------------------------------- /lambda/rapi/model/error_cause_compactor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestErrorCauseCropMessageAndWorkingDir(t *testing.T) { 15 | largeString := strings.Repeat("a", 4*MaxErrorCauseSizeBytes) 16 | factorsAndExpectedLengths := map[float64]int{ 17 | 1.5: len(largeString), 18 | 1.0: len(largeString), 19 | 0.5: len(largeString), 20 | 0.0: (MaxErrorCauseSizeBytes - paddingForFieldNames) / 2, 21 | } 22 | 23 | for factor, length := range factorsAndExpectedLengths { 24 | cause := ErrorCause{ 25 | Message: largeString, 26 | WorkingDir: largeString, 27 | } 28 | 29 | compactor := newErrorCauseCompactor(cause) 30 | compactor.crop(factor) 31 | 32 | failureMsg := fmt.Sprintf("factor: %f, length: expected=%d, actual=%d", factor, length, len(compactor.ec.Message)) 33 | assert.Len(t, compactor.ec.Message, length, "Message: "+failureMsg) 34 | assert.Len(t, compactor.ec.WorkingDir, length, "WorkingDir: "+failureMsg) 35 | } 36 | } 37 | 38 | func TestErrorCauseCropStackTraces(t *testing.T) { 39 | noOfElements := 3 * MaxErrorCauseSizeBytes 40 | largeExceptions := make([]exception, noOfElements) 41 | for i := range largeExceptions { 42 | largeExceptions[i] = exception{Message: "a"} 43 | } 44 | 45 | largePaths := make([]string, noOfElements) 46 | for i := range largePaths { 47 | largePaths[i] = "a" 48 | } 49 | 50 | factorsAndExpectedLengths := map[float64]int{ 51 | 1.5: noOfElements, 52 | 1.0: noOfElements, 53 | 0.5: int(noOfElements / 2), 54 | 0.0: 0, 55 | } 56 | 57 | for factor, length := range factorsAndExpectedLengths { 58 | cause := ErrorCause{ 59 | Exceptions: largeExceptions, 60 | Paths: largePaths, 61 | } 62 | 63 | compactor := newErrorCauseCompactor(cause) 64 | compactor.crop(factor) 65 | 66 | failureMsg := fmt.Sprintf("factor: %f, length: expected=%d, actual=%d", factor, length, len(compactor.ec.WorkingDir)) 67 | assert.Len(t, compactor.ec.Exceptions, length, "Exceptions: "+failureMsg) 68 | assert.Len(t, compactor.ec.Paths, length, "Paths: "+failureMsg) 69 | } 70 | } 71 | 72 | func TestCropString(t *testing.T) { 73 | maxLen := 5 74 | stringsAndExpectedCrops := map[string]string{ 75 | "abcde": "abcde", 76 | "abcdef": "ab...", 77 | "": "", 78 | } 79 | 80 | for str, expectedStr := range stringsAndExpectedCrops { 81 | assert.Equal(t, expectedStr, cropString(str, maxLen)) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lambda/rapi/model/errorresponse.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | // ErrorResponse is a standard invoke error response, 7 | // providing information about the error. 8 | type ErrorResponse struct { 9 | ErrorMessage string `json:"errorMessage"` 10 | ErrorType string `json:"errorType"` 11 | StackTrace []string `json:"stackTrace,omitempty"` 12 | } 13 | -------------------------------------------------------------------------------- /lambda/rapi/model/statusresponse.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | // StatusResponse is a response returned by the API server, 7 | // providing status information. 8 | type StatusResponse struct { 9 | Status string `json:"status"` 10 | } 11 | -------------------------------------------------------------------------------- /lambda/rapi/model/tracing.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | type TracingType string 7 | 8 | const ( 9 | // XRayTracingType represents an X-Ray Tracing object type 10 | XRayTracingType TracingType = "X-Amzn-Trace-Id" 11 | ) 12 | 13 | const ( 14 | XRaySampled = "1" 15 | XRayNonSampled = "0" 16 | ) 17 | 18 | // Tracing object returned as part of agent Invoke event 19 | type Tracing struct { 20 | Type TracingType `json:"type"` 21 | XRayTracing 22 | } 23 | 24 | // XRayTracing is a type of Tracing object 25 | type XRayTracing struct { 26 | Value string `json:"value"` 27 | } 28 | 29 | // NewXRayTracing returns a new XRayTracing object with specified value 30 | func NewXRayTracing(value string) *Tracing { 31 | if len(value) == 0 { 32 | return nil 33 | } 34 | 35 | return &Tracing{ 36 | XRayTracingType, 37 | XRayTracing{value}, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lambda/rapi/rendering/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /* 5 | Package rendering provides stateful event rendering service. 6 | 7 | State of the rendering service should be set from the main event dispatch thread 8 | prior to releasing threads that are registered for the event. 9 | 10 | Example of INVOKE event: 11 | 12 | [thread] // suspended in READY state 13 | 14 | [main] // set renderer for INVOKE event 15 | [main] renderingService.SetRenderer(rendering.NewInvokeRenderer()) 16 | [main] // release threads registered for INVOKE event 17 | 18 | [thread] // receives INVOKE event 19 | */ 20 | package rendering 21 | -------------------------------------------------------------------------------- /lambda/rapi/rendering/render_error.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rendering 5 | 6 | import ( 7 | "fmt" 8 | "net/http" 9 | 10 | log "github.com/sirupsen/logrus" 11 | "go.amzn.com/lambda/interop" 12 | "go.amzn.com/lambda/rapi/model" 13 | ) 14 | 15 | // RenderForbiddenWithTypeMsg method for rendering error response 16 | func RenderForbiddenWithTypeMsg(w http.ResponseWriter, r *http.Request, errorType string, format string, args ...interface{}) { 17 | if err := RenderJSON(http.StatusForbidden, w, r, &model.ErrorResponse{ 18 | ErrorType: errorType, 19 | ErrorMessage: fmt.Sprintf(format, args...), 20 | }); err != nil { 21 | log.WithError(err).Warn("Error while rendering response") 22 | http.Error(w, err.Error(), http.StatusInternalServerError) 23 | } 24 | } 25 | 26 | // RenderInternalServerError method for rendering error response 27 | func RenderInternalServerError(w http.ResponseWriter, r *http.Request) { 28 | if err := RenderJSON(http.StatusInternalServerError, w, r, &model.ErrorResponse{ 29 | ErrorMessage: "Internal Server Error", 30 | ErrorType: ErrorTypeInternalServerError, 31 | }); err != nil { 32 | log.WithError(err).Warn("Error while rendering response") 33 | http.Error(w, err.Error(), http.StatusInternalServerError) 34 | } 35 | } 36 | 37 | // RenderRequestEntityTooLarge method for rendering error response 38 | func RenderRequestEntityTooLarge(w http.ResponseWriter, r *http.Request) { 39 | if err := RenderJSON(http.StatusRequestEntityTooLarge, w, r, &model.ErrorResponse{ 40 | ErrorMessage: fmt.Sprintf("Exceeded maximum allowed payload size (%d bytes).", interop.MaxPayloadSize), 41 | ErrorType: ErrorTypeRequestEntityTooLarge, 42 | }); err != nil { 43 | log.WithError(err).Warn("Error while rendering response") 44 | http.Error(w, err.Error(), http.StatusInternalServerError) 45 | } 46 | } 47 | 48 | // RenderTruncatedHTTPRequestError method for rendering error response 49 | func RenderTruncatedHTTPRequestError(w http.ResponseWriter, r *http.Request) { 50 | if err := RenderJSON(http.StatusBadRequest, w, r, &model.ErrorResponse{ 51 | ErrorMessage: "HTTP request detected as truncated", 52 | ErrorType: ErrorTypeTruncatedHTTPRequest, 53 | }); err != nil { 54 | log.WithError(err).Warn("Error while rendering response") 55 | http.Error(w, err.Error(), http.StatusInternalServerError) 56 | } 57 | } 58 | 59 | // RenderInvalidRequestID renders invalid request ID error response 60 | func RenderInvalidRequestID(w http.ResponseWriter, r *http.Request) { 61 | if err := RenderJSON(http.StatusBadRequest, w, r, &model.ErrorResponse{ 62 | ErrorMessage: "Invalid request ID", 63 | ErrorType: "InvalidRequestID", 64 | }); err != nil { 65 | log.WithError(err).Warn("Error while rendering response") 66 | http.Error(w, err.Error(), http.StatusInternalServerError) 67 | } 68 | } 69 | 70 | // RenderInvalidFunctionResponseMode renders invalid function response mode response 71 | func RenderInvalidFunctionResponseMode(w http.ResponseWriter, r *http.Request) { 72 | if err := RenderJSON(http.StatusBadRequest, w, r, &model.ErrorResponse{ 73 | ErrorMessage: "Invalid function response mode", 74 | ErrorType: "InvalidFunctionResponseMode", 75 | }); err != nil { 76 | log.WithError(err).Warn("Error while rendering response") 77 | http.Error(w, err.Error(), http.StatusInternalServerError) 78 | } 79 | } 80 | 81 | // RenderInteropError is a convenience method for interpreting interop errors 82 | func RenderInteropError(writer http.ResponseWriter, request *http.Request, err error) { 83 | if err == interop.ErrInvalidInvokeID || err == interop.ErrResponseSent { 84 | RenderInvalidRequestID(writer, request) 85 | } else { 86 | log.Panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lambda/rapi/rendering/render_json.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rendering 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "net/http" 10 | 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // RenderJSON: 15 | // - marshals 'v' to JSON, automatically escaping HTML 16 | // - sets the Content-Type as application/json 17 | // - sets the HTTP response status code 18 | // - returns an error if it occurred before writing to response 19 | // TODO: r *http.Request is not used, remove it 20 | func RenderJSON(status int, w http.ResponseWriter, r *http.Request, v interface{}) error { 21 | buf := &bytes.Buffer{} 22 | enc := json.NewEncoder(buf) 23 | enc.SetEscapeHTML(true) 24 | if err := enc.Encode(v); err != nil { 25 | return err 26 | } 27 | 28 | w.Header().Set("Content-Type", "application/json") 29 | w.WriteHeader(status) 30 | if _, err := w.Write(buf.Bytes()); err != nil { 31 | log.WithError(err).Warn("Error while writing response body") 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /lambda/rapi/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rapi 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "testing" 13 | "time" 14 | 15 | "go.amzn.com/lambda/testdata" 16 | 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | const nextAvailablePort = 0 // net.Listener convention for next available port 21 | const serverAddress = "127.0.0.1" 22 | 23 | func createTestServer(handlerFunc http.HandlerFunc) (*Server, error) { 24 | host, server := serverAddress, &http.Server{Handler: handlerFunc} 25 | s := &Server{ 26 | host: host, 27 | port: nextAvailablePort, 28 | server: server, 29 | listener: nil, 30 | exit: make(chan error, 1), 31 | } 32 | err := s.Listen() 33 | return s, err 34 | } 35 | 36 | func TestServerReturnsSuccessfulResponse(t *testing.T) { 37 | expectedResponse := "foo" 38 | testHandler := http.HandlerFunc( 39 | func(w http.ResponseWriter, r *http.Request) { 40 | io.WriteString(w, expectedResponse) 41 | }) 42 | s, err := createTestServer(testHandler) 43 | if err != nil { 44 | assert.FailNowf(t, "Server failed to listen", err.Error()) 45 | } 46 | go func() { 47 | s.Serve(context.Background()) 48 | }() 49 | 50 | resp, err := http.Get(fmt.Sprintf("http://%s/", s.listener.Addr())) 51 | if err != nil { 52 | assert.FailNowf(t, "Failed to get response", err.Error()) 53 | } 54 | body, err := io.ReadAll(resp.Body) 55 | if err != nil { 56 | assert.FailNowf(t, "Failed to read response body", err.Error()) 57 | } 58 | resp.Body.Close() 59 | s.Close() 60 | 61 | assert.Equal(t, expectedResponse, string(body)) 62 | } 63 | 64 | func TestServerExitsOnContextCancelation(t *testing.T) { 65 | testHandler := http.HandlerFunc( 66 | func(w http.ResponseWriter, r *http.Request) { 67 | io.WriteString(w, "foo") 68 | }) 69 | ctx, cancel := context.WithCancel(context.Background()) 70 | errChan := make(chan error, 1) 71 | s, err := createTestServer(testHandler) 72 | if err != nil { 73 | assert.FailNowf(t, "Server failed to listen", err.Error()) 74 | } 75 | go func() { 76 | errChan <- s.Serve(ctx) 77 | }() 78 | 79 | pingRequestFunc := func() (bool, error) { 80 | if _, err := http.Get(s.URL("/ping")); err != nil { 81 | return false, err 82 | } 83 | return true, nil 84 | } 85 | serverStarted := testdata.Eventually(t, pingRequestFunc, 10*time.Millisecond, 10) 86 | assert.True(t, serverStarted) 87 | 88 | cancel() 89 | error := testdata.WaitForErrorWithTimeout(errChan, 2*time.Second) 90 | s.Close() 91 | assert.Error(t, error) 92 | assert.Contains(t, error.Error(), ctx.Err().Error()) 93 | } 94 | 95 | func TestServerExitsOnExitSignalFromHandler(t *testing.T) { 96 | testHandler := http.HandlerFunc( 97 | func(w http.ResponseWriter, r *http.Request) { 98 | io.WriteString(w, "foo") 99 | }) 100 | errChan := make(chan error, 1) 101 | s, err := createTestServer(testHandler) 102 | if err != nil { 103 | assert.FailNowf(t, "Server failed to listen", err.Error()) 104 | } 105 | go func() { 106 | errChan <- s.Serve(context.Background()) 107 | }() 108 | 109 | exitError := errors.New("foo bar error") 110 | s.exit <- exitError 111 | 112 | error := testdata.WaitForErrorWithTimeout(errChan, time.Second) 113 | s.Close() 114 | 115 | assert.Error(t, error) 116 | assert.Contains(t, error.Error(), exitError.Error()) 117 | } 118 | -------------------------------------------------------------------------------- /lambda/rapidcore/env/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package env 5 | 6 | func predefinedInternalEnvVarKeys() map[string]bool { 7 | return map[string]bool{ 8 | "_LAMBDA_SB_ID": true, 9 | "_LAMBDA_LOG_FD": true, 10 | "_LAMBDA_SHARED_MEM_FD": true, 11 | "_LAMBDA_CONTROL_SOCKET": true, 12 | "_LAMBDA_DIRECT_INVOKE_SOCKET": true, 13 | "_LAMBDA_RUNTIME_LOAD_TIME": true, 14 | "_LAMBDA_CONSOLE_SOCKET": true, 15 | // _X_AMZN_TRACE_ID is set by stock runtimes. Provided 16 | // runtimes should set and mutate it on each invoke. 17 | "_X_AMZN_TRACE_ID": true, 18 | "_LAMBDA_TELEMETRY_API_PASSPHRASE": true, 19 | } 20 | } 21 | 22 | func predefinedPlatformEnvVarKeys() map[string]bool { 23 | return map[string]bool{ 24 | "AWS_REGION": true, 25 | "AWS_DEFAULT_REGION": true, 26 | "AWS_LAMBDA_FUNCTION_NAME": true, 27 | "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": true, 28 | "AWS_LAMBDA_FUNCTION_VERSION": true, 29 | "AWS_LAMBDA_RUNTIME_API": true, 30 | "TZ": true, 31 | } 32 | } 33 | 34 | func predefinedRuntimeEnvVarKeys() map[string]bool { 35 | return map[string]bool{ 36 | "_HANDLER": true, 37 | "AWS_EXECUTION_ENV": true, 38 | "AWS_LAMBDA_LOG_GROUP_NAME": true, 39 | "AWS_LAMBDA_LOG_STREAM_NAME": true, 40 | "LAMBDA_TASK_ROOT": true, 41 | "LAMBDA_RUNTIME_DIR": true, 42 | } 43 | } 44 | 45 | func predefinedPlatformUnreservedEnvVarKeys() map[string]bool { 46 | return map[string]bool{ 47 | // AWS_XRAY_DAEMON_ADDRESS is unreserved but RAPID boot depends on it 48 | "AWS_XRAY_DAEMON_ADDRESS": true, 49 | } 50 | } 51 | 52 | func predefinedCredentialsEnvVarKeys() map[string]bool { 53 | return map[string]bool{ 54 | "AWS_ACCESS_KEY_ID": true, 55 | "AWS_SECRET_ACCESS_KEY": true, 56 | "AWS_SESSION_TOKEN": true, 57 | } 58 | } 59 | 60 | func extensionExcludedKeys() map[string]bool { 61 | return map[string]bool{ 62 | "AWS_XRAY_CONTEXT_MISSING": true, 63 | "_AWS_XRAY_DAEMON_ADDRESS": true, 64 | "_AWS_XRAY_DAEMON_PORT": true, 65 | "_LAMBDA_TELEMETRY_LOG_FD": true, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lambda/rapidcore/env/customer.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package env 5 | 6 | import ( 7 | "os" 8 | "strings" 9 | 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | func isInternalEnvVar(envKey string) bool { 14 | // the rule is no '_' prefixed env. variables will be propagated to the runtime but the ones explicitly exempted 15 | allowedKeys := map[string]bool{ 16 | "_HANDLER": true, 17 | "_AWS_XRAY_DAEMON_ADDRESS": true, 18 | "_AWS_XRAY_DAEMON_PORT": true, 19 | "_LAMBDA_TELEMETRY_LOG_FD": true, 20 | } 21 | return strings.HasPrefix(envKey, "_") && !allowedKeys[envKey] 22 | } 23 | 24 | // CustomerEnvironmentVariables parses all environment variables that are 25 | // not internal/credential/platform, and must be called before agent bootstrap. 26 | func CustomerEnvironmentVariables() map[string]string { 27 | internalKeys := predefinedInternalEnvVarKeys() 28 | platformKeys := predefinedPlatformEnvVarKeys() 29 | runtimeKeys := predefinedRuntimeEnvVarKeys() 30 | credentialKeys := predefinedCredentialsEnvVarKeys() 31 | platformUnreservedKeys := predefinedPlatformUnreservedEnvVarKeys() 32 | isCustomer := func(key string) bool { 33 | return !internalKeys[key] && 34 | !runtimeKeys[key] && 35 | !platformKeys[key] && 36 | !credentialKeys[key] && 37 | !platformUnreservedKeys[key] && 38 | !isInternalEnvVar(key) 39 | } 40 | 41 | customerEnv := map[string]string{} 42 | for _, keyval := range os.Environ() { 43 | key, val, err := SplitEnvironmentVariable(keyval) 44 | if err != nil { 45 | log.Warnf("Customer environment variable with invalid format: %s", err) 46 | continue 47 | } 48 | 49 | if isCustomer(key) { 50 | customerEnv[key] = val 51 | } 52 | } 53 | 54 | return customerEnv 55 | } 56 | -------------------------------------------------------------------------------- /lambda/rapidcore/env/rapidenv.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package env 5 | 6 | import ( 7 | "strconv" 8 | "syscall" 9 | 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | // RapidConfig holds config req'd for RAPID's internal 14 | // operation, parsed from internal env vars. 15 | // It should be build using `NewRapidConfig` to make sure that all the 16 | // internal invariants are respected. 17 | type RapidConfig struct { 18 | SbID string 19 | LogFd int 20 | ShmFd int 21 | CtrlFd int 22 | CnslFd int 23 | DirectInvokeFd int 24 | LambdaTaskRoot string 25 | XrayDaemonAddress string 26 | PreLoadTimeNs int64 27 | FunctionName string 28 | TelemetryAPIPassphrase string 29 | } 30 | 31 | // Build the `RapidConfig` struct checking all the internal invariants 32 | func NewRapidConfig(e *Environment) RapidConfig { 33 | return RapidConfig{ 34 | SbID: getStrEnvVarOrDie(e.rapid, "_LAMBDA_SB_ID"), 35 | LogFd: getSocketEnvVarOrDie(e.rapid, "_LAMBDA_LOG_FD"), 36 | ShmFd: getSocketEnvVarOrDie(e.rapid, "_LAMBDA_SHARED_MEM_FD"), 37 | CtrlFd: getSocketEnvVarOrDie(e.rapid, "_LAMBDA_CONTROL_SOCKET"), 38 | CnslFd: getSocketEnvVarOrDie(e.rapid, "_LAMBDA_CONSOLE_SOCKET"), 39 | DirectInvokeFd: getOptionalSocketEnvVar(e.rapid, "_LAMBDA_DIRECT_INVOKE_SOCKET"), 40 | PreLoadTimeNs: getInt64EnvVarOrDie(e.rapid, "_LAMBDA_RUNTIME_LOAD_TIME"), 41 | LambdaTaskRoot: getStrEnvVarOrDie(e.runtime, "LAMBDA_TASK_ROOT"), 42 | XrayDaemonAddress: getStrEnvVarOrDie(e.platformUnreserved, "AWS_XRAY_DAEMON_ADDRESS"), 43 | FunctionName: getStrEnvVarOrDie(e.platform, "AWS_LAMBDA_FUNCTION_NAME"), 44 | TelemetryAPIPassphrase: e.rapid["_LAMBDA_TELEMETRY_API_PASSPHRASE"], // TODO: Die if not set 45 | } 46 | } 47 | 48 | func getStrEnvVarOrDie(env map[string]string, name string) string { 49 | val, ok := env[name] 50 | if !ok { 51 | log.WithField("name", name).Fatal("Environment variable is not set") 52 | } 53 | return val 54 | } 55 | 56 | func getInt64EnvVarOrDie(env map[string]string, name string) int64 { 57 | strval := getStrEnvVarOrDie(env, name) 58 | val, err := strconv.ParseInt(strval, 10, 64) 59 | if err != nil { 60 | log.WithError(err).WithField("name", name).Fatal("Unable to parse int env var.") 61 | } 62 | return val 63 | } 64 | 65 | func getIntEnvVarOrDie(env map[string]string, name string) int { 66 | return int(getInt64EnvVarOrDie(env, name)) 67 | } 68 | 69 | // getSocketEnvVarOrDie reads and returns an int value of the 70 | // environment variable or dies, when unable to do so. 71 | // It also makes CloseOnExec for this value. 72 | func getSocketEnvVarOrDie(env map[string]string, name string) int { 73 | sock := getIntEnvVarOrDie(env, name) 74 | syscall.CloseOnExec(sock) 75 | return sock 76 | } 77 | 78 | // returns -1 if env variable was not set. Exits if it holds unexpected (non-int) value 79 | func getOptionalSocketEnvVar(env map[string]string, name string) int { 80 | val, found := env[name] 81 | if !found { 82 | return -1 83 | } 84 | 85 | sock, err := strconv.Atoi(val) 86 | if err != nil { 87 | log.WithError(err).WithField("name", name).Fatal("Unable to parse socket env var.") 88 | } 89 | 90 | if sock < 0 { 91 | log.WithError(err).WithField("name", name).Fatal("Negative socket descriptor value") 92 | } 93 | 94 | syscall.CloseOnExec(sock) 95 | return sock 96 | } 97 | -------------------------------------------------------------------------------- /lambda/rapidcore/env/util.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package env 5 | 6 | import ( 7 | "errors" 8 | "strings" 9 | ) 10 | 11 | func SplitEnvironmentVariable(envKeyVal string) (string, string, error) { 12 | splitKeyVal := strings.SplitN(envKeyVal, "=", 2) // values can contain '=' 13 | if len(splitKeyVal) < 2 { 14 | return "", "", errors.New("could not split env var by '=' delimiter") 15 | } 16 | return splitKeyVal[0], splitKeyVal[1], nil 17 | } 18 | -------------------------------------------------------------------------------- /lambda/rapidcore/env/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package env 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestEnvironmentVariableSplitting(t *testing.T) { 13 | envVar := "FOO=BAR" 14 | k, v, err := SplitEnvironmentVariable(envVar) 15 | assert.NoError(t, err) 16 | assert.Equal(t, k, "FOO") 17 | assert.Equal(t, v, "BAR") 18 | 19 | envVar = "FOO=BAR=BAZ" 20 | k, v, err = SplitEnvironmentVariable(envVar) 21 | assert.NoError(t, err) 22 | assert.Equal(t, k, "FOO") 23 | assert.Equal(t, v, "BAR=BAZ") 24 | 25 | envVar = "FOO=" 26 | k, v, err = SplitEnvironmentVariable(envVar) 27 | assert.NoError(t, err) 28 | assert.Equal(t, k, "FOO") 29 | assert.Equal(t, v, "") 30 | 31 | envVar = "FOO" 32 | k, v, err = SplitEnvironmentVariable(envVar) 33 | assert.Error(t, err) 34 | assert.Equal(t, k, "") 35 | assert.Equal(t, v, "") 36 | } 37 | -------------------------------------------------------------------------------- /lambda/rapidcore/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rapidcore 5 | 6 | import "errors" 7 | 8 | var ErrInitDoneFailed = errors.New("InitDoneFailed") 9 | var ErrInitNotStarted = errors.New("InitNotStarted") 10 | var ErrInitResetReceived = errors.New("InitResetReceived") 11 | 12 | var ErrNotReserved = errors.New("NotReserved") 13 | var ErrAlreadyReserved = errors.New("AlreadyReserved") 14 | var ErrAlreadyReplied = errors.New("AlreadyReplied") 15 | var ErrAlreadyInvocating = errors.New("AlreadyInvocating") 16 | var ErrReserveReservationDone = errors.New("ReserveReservationDone") 17 | 18 | var ErrInvokeResponseAlreadyWritten = errors.New("InvokeResponseAlreadyWritten") 19 | var ErrInvokeDoneFailed = errors.New("InvokeDoneFailed") 20 | var ErrInvokeReservationDone = errors.New("InvokeReservationDone") 21 | 22 | var ErrReleaseReservationDone = errors.New("ReleaseReservationDone") 23 | 24 | var ErrInternalServerError = errors.New("InternalServerError") 25 | var ErrInvokeTimeout = errors.New("InvokeTimeout") 26 | -------------------------------------------------------------------------------- /lambda/rapidcore/runtime_release.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rapidcore 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | type Logging string 14 | 15 | const ( 16 | AmznStdout Logging = "amzn-stdout" 17 | AmznStdoutTLV Logging = "amzn-stdout-tlv" 18 | ) 19 | 20 | // RuntimeRelease stores runtime identification data 21 | type RuntimeRelease struct { 22 | Name string 23 | Version string 24 | Logging Logging 25 | } 26 | 27 | const RuntimeReleasePath = "/var/runtime/runtime-release" 28 | 29 | // GetRuntimeRelease reads Runtime identification data from config file and parses it into a struct 30 | func GetRuntimeRelease(path string) (*RuntimeRelease, error) { 31 | pairs, err := ParsePropertiesFile(path) 32 | if err != nil { 33 | return nil, fmt.Errorf("could not parse %s: %w", path, err) 34 | } 35 | 36 | return &RuntimeRelease{pairs["NAME"], pairs["VERSION"], Logging(pairs["LOGGING"])}, nil 37 | } 38 | 39 | // ParsePropertiesFile reads key-value pairs from file in newline-separated list of environment-like 40 | // shell-compatible variable assignments. 41 | // Format: https://www.freedesktop.org/software/systemd/man/os-release.html 42 | // Value quotes are trimmed. Latest write wins for duplicated keys. 43 | func ParsePropertiesFile(path string) (map[string]string, error) { 44 | f, err := os.Open(path) 45 | if err != nil { 46 | return nil, fmt.Errorf("could not open %s: %w", path, err) 47 | } 48 | defer f.Close() 49 | 50 | pairs := make(map[string]string) 51 | 52 | s := bufio.NewScanner(f) 53 | for s.Scan() { 54 | if s.Text() == "" || strings.HasPrefix(s.Text(), "#") { 55 | continue 56 | } 57 | k, v, found := strings.Cut(s.Text(), "=") 58 | if !found { 59 | return nil, fmt.Errorf("could not parse key-value pair from a line: %s", s.Text()) 60 | } 61 | pairs[k] = strings.Trim(v, "'\"") 62 | } 63 | if err := s.Err(); err != nil { 64 | return nil, fmt.Errorf("failed to read properties file: %w", err) 65 | } 66 | 67 | return pairs, nil 68 | } 69 | -------------------------------------------------------------------------------- /lambda/rapidcore/runtime_release_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rapidcore 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestGetRuntimeRelease(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | content string 18 | want *RuntimeRelease 19 | }{ 20 | { 21 | "simple", 22 | "NAME=foo\nVERSION=bar\nLOGGING=baz\n", 23 | &RuntimeRelease{"foo", "bar", "baz"}, 24 | }, 25 | { 26 | "no trailing new line", 27 | "NAME=foo\nVERSION=bar\nLOGGING=baz", 28 | &RuntimeRelease{"foo", "bar", "baz"}, 29 | }, 30 | { 31 | "nonexistent keys", 32 | "LOGGING=baz\n", 33 | &RuntimeRelease{"", "", "baz"}, 34 | }, 35 | { 36 | "empty value", 37 | "NAME=\nVERSION=\nLOGGING=\n", 38 | &RuntimeRelease{"", "", ""}, 39 | }, 40 | { 41 | "delimiter in value", 42 | "NAME=Foo=Bar\nVERSION=bar\nLOGGING=baz\n", 43 | &RuntimeRelease{"Foo=Bar", "bar", "baz"}, 44 | }, 45 | { 46 | "empty file", 47 | "", 48 | &RuntimeRelease{"", "", ""}, 49 | }, 50 | { 51 | "quotes", 52 | "NAME=\"foo\"\nVERSION='bar'\n", 53 | &RuntimeRelease{"foo", "bar", ""}, 54 | }, 55 | { 56 | "double quotes", 57 | "NAME='\"foo\"'\nVERSION=\"'bar'\"\n", 58 | &RuntimeRelease{"foo", "bar", ""}, 59 | }, 60 | { 61 | "empty lines", // production runtime-release files have empty line in the end of the file 62 | "\nNAME=foo\n\nVERSION=bar\n\nLOGGING=baz\n\n", 63 | &RuntimeRelease{"foo", "bar", "baz"}, 64 | }, 65 | { 66 | "comments", 67 | "# comment 1\nNAME=foo\n# comment 2\nVERSION=bar\n# comment 3\nLOGGING=baz\n# comment 4\n", 68 | &RuntimeRelease{"foo", "bar", "baz"}, 69 | }, 70 | } 71 | 72 | for _, tt := range tests { 73 | t.Run(tt.name, func(t *testing.T) { 74 | f, err := os.CreateTemp(os.TempDir(), "runtime-release") 75 | require.NoError(t, err) 76 | _, err = f.WriteString(tt.content) 77 | require.NoError(t, err) 78 | got, err := GetRuntimeRelease(f.Name()) 79 | assert.NoError(t, err) 80 | assert.Equal(t, tt.want, got) 81 | }) 82 | } 83 | } 84 | 85 | func TestGetRuntimeRelease_NotFound(t *testing.T) { 86 | _, err := GetRuntimeRelease("/sys/not-exists") 87 | assert.Error(t, err) 88 | } 89 | 90 | func TestGetRuntimeRelease_InvalidLine(t *testing.T) { 91 | f, err := os.CreateTemp(os.TempDir(), "runtime-release") 92 | require.NoError(t, err) 93 | _, err = f.WriteString("NAME=foo\nVERSION=bar\nLOGGING=baz\nSOMETHING") 94 | require.NoError(t, err) 95 | _, err = GetRuntimeRelease(f.Name()) 96 | assert.Error(t, err) 97 | } 98 | -------------------------------------------------------------------------------- /lambda/rapidcore/sandbox_emulator_api.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rapidcore 5 | 6 | import ( 7 | "go.amzn.com/lambda/interop" 8 | 9 | "net/http" 10 | ) 11 | 12 | // LambdaInvokeAPI are the methods used by the Runtime Interface Emulator 13 | type LambdaInvokeAPI interface { 14 | Init(i *interop.Init, invokeTimeoutMs int64) 15 | Invoke(responseWriter http.ResponseWriter, invoke *interop.Invoke) error 16 | } 17 | 18 | // EmulatorAPI wraps the standalone interop server to provide a convenient interface 19 | // for Rapid Standalone 20 | type EmulatorAPI struct { 21 | server *Server 22 | } 23 | 24 | // Validate interface compliance 25 | var _ LambdaInvokeAPI = (*EmulatorAPI)(nil) 26 | 27 | func NewEmulatorAPI(s *Server) *EmulatorAPI { 28 | return &EmulatorAPI{s} 29 | } 30 | 31 | // Init method is only used by the Runtime interface emulator 32 | func (l *EmulatorAPI) Init(i *interop.Init, timeoutMs int64) { 33 | l.server.Init(&interop.Init{ 34 | AccountID: i.AccountID, 35 | Handler: i.Handler, 36 | AwsKey: i.AwsKey, 37 | AwsSecret: i.AwsSecret, 38 | AwsSession: i.AwsSession, 39 | XRayDaemonAddress: i.XRayDaemonAddress, 40 | FunctionName: i.FunctionName, 41 | FunctionVersion: i.FunctionVersion, 42 | CustomerEnvironmentVariables: i.CustomerEnvironmentVariables, 43 | RuntimeInfo: i.RuntimeInfo, 44 | SandboxType: i.SandboxType, 45 | Bootstrap: i.Bootstrap, 46 | EnvironmentVariables: i.EnvironmentVariables, 47 | }, timeoutMs) 48 | } 49 | 50 | // Invoke method is only used by the Runtime interface emulator 51 | func (l *EmulatorAPI) Invoke(w http.ResponseWriter, i *interop.Invoke) error { 52 | return l.server.Invoke(w, i) 53 | } 54 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/directInvokeHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "go.amzn.com/lambda/rapidcore" 8 | 9 | "net/http" 10 | 11 | log "github.com/sirupsen/logrus" 12 | "go.amzn.com/lambda/core/directinvoke" 13 | ) 14 | 15 | func DirectInvokeHandler(w http.ResponseWriter, r *http.Request, s InteropServer) { 16 | tok := s.CurrentToken() 17 | if tok == nil { 18 | log.Errorf("Attempt to call directInvoke without Reserve") 19 | w.WriteHeader(http.StatusBadRequest) 20 | return 21 | } 22 | 23 | invoke, err := directinvoke.ReceiveDirectInvoke(w, r, *tok) 24 | if err != nil { 25 | log.Errorf("direct invoke error: %s", err) 26 | return 27 | } 28 | 29 | if err := s.AwaitInitialized(); err != nil { 30 | w.WriteHeader(DoneFailedHTTPCode) 31 | if state, err := s.InternalState(); err == nil { 32 | w.Write(state.AsJSON()) 33 | } 34 | return 35 | } 36 | 37 | if err := s.FastInvoke(w, invoke, true); err != nil { 38 | switch err { 39 | case rapidcore.ErrNotReserved: 40 | case rapidcore.ErrAlreadyReplied: 41 | case rapidcore.ErrAlreadyInvocating: 42 | log.Errorf("Failed to set reply stream: %s", err) 43 | w.WriteHeader(http.StatusBadRequest) 44 | return 45 | case rapidcore.ErrInvokeReservationDone: 46 | w.WriteHeader(http.StatusBadGateway) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/eventLogHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | 11 | "go.amzn.com/lambda/rapidcore/standalone/telemetry" 12 | ) 13 | 14 | func EventLogHandler(w http.ResponseWriter, r *http.Request, eventsAPI *telemetry.StandaloneEventsAPI) { 15 | bytes, err := json.Marshal(eventsAPI.EventLog()) 16 | if err != nil { 17 | http.Error(w, fmt.Sprintf("marshalling error: %s", err), http.StatusInternalServerError) 18 | return 19 | } 20 | w.Write(bytes) 21 | } 22 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/executeHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "net/http" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "go.amzn.com/lambda/interop" 11 | "go.amzn.com/lambda/metering" 12 | "go.amzn.com/lambda/rapidcore" 13 | ) 14 | 15 | func Execute(w http.ResponseWriter, r *http.Request, sandbox rapidcore.LambdaInvokeAPI) { 16 | 17 | invokePayload := &interop.Invoke{ 18 | TraceID: r.Header.Get("X-Amzn-Trace-Id"), 19 | LambdaSegmentID: r.Header.Get("X-Amzn-Segment-Id"), 20 | Payload: r.Body, 21 | InvokeReceivedTime: metering.Monotime(), 22 | } 23 | 24 | // If we write to 'w' directly and waitUntilRelease fails, we won't be able to propagate error anymore 25 | invokeResp := &ResponseWriterProxy{} 26 | if err := sandbox.Invoke(invokeResp, invokePayload); err != nil { 27 | switch err { 28 | // Reserve errors: 29 | case rapidcore.ErrAlreadyReserved: 30 | log.WithError(err).Error("Failed to reserve as it is already reserved.") 31 | w.WriteHeader(400) 32 | case rapidcore.ErrInternalServerError: 33 | log.WithError(err).Error("Failed to reserve from an internal server error.") 34 | w.WriteHeader(http.StatusInternalServerError) 35 | 36 | // Invoke errors: 37 | case rapidcore.ErrNotReserved, rapidcore.ErrAlreadyReplied, rapidcore.ErrAlreadyInvocating: 38 | log.WithError(err).Error("Failed to invoke from setting the reply stream.") 39 | w.WriteHeader(400) 40 | 41 | case rapidcore.ErrInvokeResponseAlreadyWritten: 42 | return 43 | case rapidcore.ErrInvokeTimeout, rapidcore.ErrInitResetReceived: 44 | log.WithError(err).Error("Failed to invoke from an invoke timeout.") 45 | w.WriteHeader(http.StatusGatewayTimeout) 46 | 47 | // DONE failures: 48 | case rapidcore.ErrInvokeDoneFailed: 49 | copyHeaders(invokeResp, w) 50 | w.WriteHeader(DoneFailedHTTPCode) 51 | w.Write(invokeResp.Body) 52 | return 53 | // Reservation canceled errors 54 | case rapidcore.ErrReserveReservationDone, rapidcore.ErrInvokeReservationDone, rapidcore.ErrReleaseReservationDone, rapidcore.ErrInitNotStarted: 55 | log.WithError(err).Error("Failed to cancel reservation.") 56 | w.WriteHeader(http.StatusGatewayTimeout) 57 | } 58 | 59 | return 60 | } 61 | 62 | copyHeaders(invokeResp, w) 63 | if invokeResp.StatusCode != 0 { 64 | w.WriteHeader(invokeResp.StatusCode) 65 | } 66 | w.Write(invokeResp.Body) 67 | } 68 | 69 | func copyHeaders(proxyWriter, writer http.ResponseWriter) { 70 | for key, val := range proxyWriter.Header() { 71 | writer.Header().Set(key, val[0]) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/initHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "time" 11 | 12 | "go.amzn.com/lambda/interop" 13 | "go.amzn.com/lambda/rapidcore/env" 14 | ) 15 | 16 | type RuntimeInfo struct { 17 | ImageJSON string `json:"runtimeImageJSON,omitempty"` 18 | Arn string `json:"runtimeArn,omitempty"` 19 | Version string `json:"runtimeVersion,omitempty"` 20 | } 21 | 22 | // TODO: introduce suppress init flag 23 | type InitBody struct { 24 | Handler string `json:"handler"` 25 | FunctionName string `json:"functionName"` 26 | FunctionVersion string `json:"functionVersion"` 27 | InvokeTimeoutMs int64 `json:"invokeTimeoutMs"` 28 | RuntimeInfo RuntimeInfo `json:"runtimeInfo"` 29 | Customer struct { 30 | Environment map[string]string `json:"environment"` 31 | } `json:"customer"` 32 | AwsKey *string `json:"awskey"` 33 | AwsSecret *string `json:"awssecret"` 34 | AwsSession *string `json:"awssession"` 35 | CredentialsExpiry time.Time `json:"credentialsExpiry"` 36 | Throttled bool `json:"throttled"` 37 | } 38 | 39 | type InitRequest struct { 40 | InitBody 41 | ReplyChan chan Reply 42 | } 43 | 44 | func (c *InitBody) Validate() error { 45 | // Handler is optional 46 | if c.FunctionName == "" { 47 | return fmt.Errorf("functionName missing") 48 | } 49 | if c.FunctionVersion == "" { 50 | return fmt.Errorf("FunctionVersion missing") 51 | } 52 | if c.InvokeTimeoutMs == 0 { 53 | return fmt.Errorf("invokeTimeoutMs missing") 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func InitHandler(w http.ResponseWriter, r *http.Request, sandbox InteropServer, bs interop.Bootstrap) { 60 | init := InitBody{} 61 | if lerr := readBodyAndUnmarshalJSON(r, &init); lerr != nil { 62 | lerr.Send(w, r) 63 | return 64 | } 65 | 66 | if err := init.Validate(); err != nil { 67 | newErrorReply(ClientInvalidRequest, err.Error()).Send(w, r) 68 | return 69 | } 70 | 71 | for envKey, envVal := range init.Customer.Environment { 72 | // We set environment variables to keep the env parsing & filtering 73 | // logic consistent across standalone-mode and girp-mode 74 | os.Setenv(envKey, envVal) 75 | } 76 | 77 | awsKey, awsSecret, awsSession := getCredentials(init) 78 | 79 | sandboxType := interop.SandboxClassic 80 | 81 | if init.Throttled { 82 | sandboxType = interop.SandboxPreWarmed 83 | } 84 | 85 | // pass to rapid 86 | sandbox.Init(&interop.Init{ 87 | Handler: init.Handler, 88 | AwsKey: awsKey, 89 | AwsSecret: awsSecret, 90 | AwsSession: awsSession, 91 | CredentialsExpiry: init.CredentialsExpiry, 92 | XRayDaemonAddress: "0.0.0.0:0", // TODO 93 | FunctionName: init.FunctionName, 94 | FunctionVersion: init.FunctionVersion, 95 | RuntimeInfo: interop.RuntimeInfo{ 96 | ImageJSON: init.RuntimeInfo.ImageJSON, 97 | Arn: init.RuntimeInfo.Arn, 98 | Version: init.RuntimeInfo.Version}, 99 | CustomerEnvironmentVariables: env.CustomerEnvironmentVariables(), 100 | SandboxType: sandboxType, 101 | Bootstrap: bs, 102 | EnvironmentVariables: env.NewEnvironment(), 103 | }, init.InvokeTimeoutMs) 104 | } 105 | 106 | func getCredentials(init InitBody) (string, string, string) { 107 | // ToDo(guvfatih): I think instead of passing and getting these credentials values via environment variables 108 | // we need to make StandaloneTests passing these via the Init request to be compliant with the existing protocol. 109 | awsKey := os.Getenv("AWS_ACCESS_KEY_ID") 110 | awsSecret := os.Getenv("AWS_SECRET_ACCESS_KEY") 111 | awsSession := os.Getenv("AWS_SESSION_TOKEN") 112 | 113 | if init.AwsKey != nil { 114 | awsKey = *init.AwsKey 115 | } 116 | 117 | if init.AwsSecret != nil { 118 | awsSecret = *init.AwsSecret 119 | } 120 | 121 | if init.AwsSession != nil { 122 | awsSession = *init.AwsSession 123 | } 124 | 125 | return awsKey, awsSecret, awsSession 126 | } 127 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/internalStateHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "net/http" 8 | ) 9 | 10 | func InternalStateHandler(w http.ResponseWriter, r *http.Request, s InteropServer) { 11 | state, err := s.InternalState() 12 | if err != nil { 13 | http.Error(w, "internal state callback not set", http.StatusInternalServerError) 14 | return 15 | } 16 | 17 | w.Write(state.AsJSON()) 18 | } 19 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/invokeHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "fmt" 8 | "net/http" 9 | "strconv" 10 | 11 | "go.amzn.com/lambda/interop" 12 | "go.amzn.com/lambda/metering" 13 | "go.amzn.com/lambda/rapidcore" 14 | 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | func InvokeHandler(w http.ResponseWriter, r *http.Request, s InteropServer) { 19 | tok := s.CurrentToken() 20 | if tok == nil { 21 | log.Errorf("Attempt to call directInvoke without Reserve") 22 | w.WriteHeader(http.StatusBadRequest) 23 | return 24 | } 25 | 26 | restoreDurationHeader := r.Header.Get("restore-duration") 27 | restoreStartHeader := r.Header.Get("restore-start-time") 28 | 29 | var restoreDurationNs int64 = 0 30 | var restoreStartTimeMonotime int64 = 0 31 | if restoreDurationHeader != "" && restoreStartHeader != "" { 32 | var err1, err2 error 33 | restoreDurationNs, err1 = strconv.ParseInt(restoreDurationHeader, 10, 64) 34 | restoreStartTimeMonotime, err2 = strconv.ParseInt(restoreStartHeader, 10, 64) 35 | if err1 != nil || err2 != nil { 36 | log.Errorf("Failed to parse 'restore-duration' from '%s' and/or 'restore-start-time' from '%s'", restoreDurationHeader, restoreStartHeader) 37 | restoreDurationNs = 0 38 | restoreStartTimeMonotime = 0 39 | } 40 | } 41 | 42 | invokePayload := &interop.Invoke{ 43 | TraceID: r.Header.Get("X-Amzn-Trace-Id"), 44 | LambdaSegmentID: r.Header.Get("X-Amzn-Segment-Id"), 45 | Payload: r.Body, 46 | DeadlineNs: fmt.Sprintf("%d", metering.Monotime()+tok.FunctionTimeout.Nanoseconds()), 47 | InvokeReceivedTime: metering.Monotime(), 48 | RestoreDurationNs: restoreDurationNs, 49 | RestoreStartTimeMonotime: restoreStartTimeMonotime, 50 | } 51 | 52 | if err := s.AwaitInitialized(); err != nil { 53 | w.WriteHeader(DoneFailedHTTPCode) 54 | if state, err := s.InternalState(); err == nil { 55 | w.Write(state.AsJSON()) 56 | } 57 | return 58 | } 59 | 60 | if err := s.FastInvoke(w, invokePayload, false); err != nil { 61 | switch err { 62 | case rapidcore.ErrNotReserved: 63 | case rapidcore.ErrAlreadyReplied: 64 | case rapidcore.ErrAlreadyInvocating: 65 | log.Errorf("Failed to set reply stream: %s", err) 66 | w.WriteHeader(400) 67 | return 68 | case rapidcore.ErrInvokeReservationDone: 69 | // TODO use http.StatusBadGateway 70 | w.WriteHeader(http.StatusGatewayTimeout) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/middleware.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/go-chi/chi/middleware" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | func standaloneAccessLogDecorator(next http.Handler) http.Handler { 14 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | log.Debugf("standalone: -> %s %s %v", r.Method, r.URL, r.Header) 16 | ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) 17 | next.ServeHTTP(ww, r) 18 | 19 | status := 200 20 | if ww.Status() != 0 { 21 | status = ww.Status() 22 | } 23 | 24 | if status != 0 && status/100 != 2 { 25 | log.Errorf("standalone: <- %s %d %v", r.URL, status, w.Header()) 26 | } else { 27 | log.Debugf("standalone: <- %s %d %v", r.URL, status, w.Header()) 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/pingHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "net/http" 8 | ) 9 | 10 | func PingHandler(w http.ResponseWriter, r *http.Request) { 11 | w.Write([]byte("pong")) 12 | } 13 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/reserveHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "net/http" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "go.amzn.com/lambda/core/directinvoke" 11 | "go.amzn.com/lambda/interop" 12 | "go.amzn.com/lambda/rapidcore" 13 | ) 14 | 15 | const ( 16 | ReservationTokenHeader = "Reservation-Token" 17 | InvokeIDHeader = "Invoke-ID" 18 | VersionIDHeader = "Version-ID" 19 | ) 20 | 21 | func tokenToHeaders(w http.ResponseWriter, token interop.Token) { 22 | w.Header().Set(ReservationTokenHeader, token.ReservationToken) 23 | w.Header().Set(directinvoke.InvokeIDHeader, token.InvokeID) 24 | w.Header().Set(directinvoke.VersionIDHeader, token.VersionID) 25 | } 26 | 27 | func ReserveHandler(w http.ResponseWriter, r *http.Request, s InteropServer) { 28 | reserveResp, err := s.Reserve("", r.Header.Get("X-Amzn-Trace-Id"), r.Header.Get("X-Amzn-Segment-Id")) 29 | 30 | if err != nil { 31 | switch err { 32 | case rapidcore.ErrReserveReservationDone: 33 | // TODO use http.StatusBadGateway 34 | w.WriteHeader(http.StatusGatewayTimeout) 35 | default: 36 | log.Errorf("Failed to reserve: %s", err) 37 | w.WriteHeader(400) 38 | } 39 | return 40 | } 41 | 42 | tokenToHeaders(w, reserveResp.Token) 43 | w.Write(reserveResp.InternalState.AsJSON()) 44 | } 45 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/resetHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "net/http" 8 | ) 9 | 10 | type resetAPIRequest struct { 11 | Reason string `json:"reason"` 12 | TimeoutMs int64 `json:"timeoutMs"` 13 | } 14 | 15 | func ResetHandler(w http.ResponseWriter, r *http.Request, s InteropServer) { 16 | reset := resetAPIRequest{} 17 | if lerr := readBodyAndUnmarshalJSON(r, &reset); lerr != nil { 18 | lerr.Send(w, r) 19 | return 20 | } 21 | 22 | resetDescription, err := s.Reset(reset.Reason, reset.TimeoutMs) 23 | if err != nil { 24 | (&FailureReply{}).Send(w, r) 25 | return 26 | } 27 | 28 | w.Write(resetDescription.AsJSON()) 29 | } 30 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/restoreHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "encoding/json" 8 | "net/http" 9 | "strconv" 10 | "time" 11 | 12 | log "github.com/sirupsen/logrus" 13 | "go.amzn.com/lambda/interop" 14 | ) 15 | 16 | type RestoreBody struct { 17 | AwsKey string `json:"awskey"` 18 | AwsSecret string `json:"awssecret"` 19 | AwsSession string `json:"awssession"` 20 | CredentialsExpiry time.Time `json:"credentialsExpiry"` 21 | RestoreHookTimeoutMs int64 `json:"restoreHookTimeoutMs"` 22 | } 23 | 24 | func RestoreHandler(w http.ResponseWriter, r *http.Request, s InteropServer) { 25 | restoreRequest := RestoreBody{} 26 | if lerr := readBodyAndUnmarshalJSON(r, &restoreRequest); lerr != nil { 27 | lerr.Send(w, r) 28 | return 29 | } 30 | 31 | restore := &interop.Restore{ 32 | AwsKey: restoreRequest.AwsKey, 33 | AwsSecret: restoreRequest.AwsSecret, 34 | AwsSession: restoreRequest.AwsSession, 35 | CredentialsExpiry: restoreRequest.CredentialsExpiry, 36 | RestoreHookTimeoutMs: restoreRequest.RestoreHookTimeoutMs, 37 | } 38 | 39 | restoreResult, err := s.Restore(restore) 40 | 41 | responseMap := make(map[string]string) 42 | 43 | responseMap["restoreMs"] = strconv.FormatInt(restoreResult.RestoreMs, 10) 44 | 45 | if err != nil { 46 | log.Errorf("Failed to restore: %s", err) 47 | responseMap["restoreError"] = err.Error() 48 | w.WriteHeader(http.StatusBadGateway) 49 | } 50 | 51 | responseJSON, err := json.Marshal(responseMap) 52 | 53 | if err != nil { 54 | log.Panicf("Cannot marshal the response map for RESTORE, %v", responseMap) 55 | } 56 | 57 | w.Write(responseJSON) 58 | } 59 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/router.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | 10 | "go.amzn.com/lambda/core/statejson" 11 | "go.amzn.com/lambda/interop" 12 | "go.amzn.com/lambda/rapidcore" 13 | "go.amzn.com/lambda/rapidcore/standalone/telemetry" 14 | 15 | "github.com/go-chi/chi" 16 | ) 17 | 18 | type InteropServer interface { 19 | Init(i *interop.Init, invokeTimeoutMs int64) error 20 | AwaitInitialized() error 21 | FastInvoke(w http.ResponseWriter, i *interop.Invoke, direct bool) error 22 | Reserve(id string, traceID, lambdaSegmentID string) (*rapidcore.ReserveResponse, error) 23 | Reset(reason string, timeoutMs int64) (*statejson.ResetDescription, error) 24 | AwaitRelease() (*statejson.ReleaseResponse, error) 25 | Shutdown(shutdown *interop.Shutdown) *statejson.InternalStateDescription 26 | InternalState() (*statejson.InternalStateDescription, error) 27 | CurrentToken() *interop.Token 28 | Restore(restore *interop.Restore) (interop.RestoreResult, error) 29 | } 30 | 31 | func NewHTTPRouter(ipcSrv InteropServer, lambdaInvokeAPI rapidcore.LambdaInvokeAPI, eventsAPI *telemetry.StandaloneEventsAPI, shutdownFunc context.CancelFunc, bs interop.Bootstrap) *chi.Mux { 32 | r := chi.NewRouter() 33 | r.Use(standaloneAccessLogDecorator) 34 | 35 | r.Post("/2015-03-31/functions/*/invocations", func(w http.ResponseWriter, r *http.Request) { Execute(w, r, lambdaInvokeAPI) }) 36 | r.Get("/test/ping", func(w http.ResponseWriter, r *http.Request) { PingHandler(w, r) }) 37 | r.Post("/test/init", func(w http.ResponseWriter, r *http.Request) { InitHandler(w, r, ipcSrv, bs) }) 38 | r.Post("/test/waitUntilInitialized", func(w http.ResponseWriter, r *http.Request) { WaitUntilInitializedHandler(w, r, ipcSrv) }) 39 | r.Post("/test/reserve", func(w http.ResponseWriter, r *http.Request) { ReserveHandler(w, r, ipcSrv) }) 40 | r.Post("/test/invoke", func(w http.ResponseWriter, r *http.Request) { InvokeHandler(w, r, ipcSrv) }) 41 | r.Post("/test/waitUntilRelease", func(w http.ResponseWriter, r *http.Request) { WaitUntilReleaseHandler(w, r, ipcSrv) }) 42 | r.Post("/test/reset", func(w http.ResponseWriter, r *http.Request) { ResetHandler(w, r, ipcSrv) }) 43 | r.Post("/test/shutdown", func(w http.ResponseWriter, r *http.Request) { ShutdownHandler(w, r, ipcSrv, shutdownFunc) }) 44 | r.Post("/test/directInvoke/{reservationtoken}", func(w http.ResponseWriter, r *http.Request) { DirectInvokeHandler(w, r, ipcSrv) }) 45 | r.Get("/test/internalState", func(w http.ResponseWriter, r *http.Request) { InternalStateHandler(w, r, ipcSrv) }) 46 | r.Get("/test/eventLog", func(w http.ResponseWriter, r *http.Request) { EventLogHandler(w, r, eventsAPI) }) 47 | r.Post("/test/restore", func(w http.ResponseWriter, r *http.Request) { RestoreHandler(w, r, ipcSrv) }) 48 | return r 49 | } 50 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/shutdownHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | 10 | "go.amzn.com/lambda/interop" 11 | "go.amzn.com/lambda/metering" 12 | ) 13 | 14 | type shutdownAPIRequest struct { 15 | TimeoutMs int64 `json:"timeoutMs"` 16 | } 17 | 18 | func ShutdownHandler(w http.ResponseWriter, r *http.Request, s InteropServer, shutdownFunc context.CancelFunc) { 19 | shutdown := shutdownAPIRequest{} 20 | if lerr := readBodyAndUnmarshalJSON(r, &shutdown); lerr != nil { 21 | lerr.Send(w, r) 22 | return 23 | } 24 | 25 | internalState := s.Shutdown(&interop.Shutdown{ 26 | DeadlineNs: metering.Monotime() + int64(shutdown.TimeoutMs*1000*1000), 27 | }) 28 | 29 | w.Write(internalState.AsJSON()) 30 | 31 | shutdownFunc() 32 | } 33 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/telemetry/agent_writer.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package telemetry 5 | 6 | import ( 7 | "bufio" 8 | "bytes" 9 | ) 10 | 11 | type SandboxAgentWriter struct { 12 | eventType string // 'runtime' or 'extension' 13 | eventsAPI *StandaloneEventsAPI 14 | } 15 | 16 | func NewSandboxAgentWriter(api *StandaloneEventsAPI, source string) *SandboxAgentWriter { 17 | return &SandboxAgentWriter{ 18 | eventType: source, 19 | eventsAPI: api, 20 | } 21 | } 22 | 23 | func (w *SandboxAgentWriter) Write(logline []byte) (int, error) { 24 | scanner := bufio.NewScanner(bytes.NewReader(logline)) 25 | scanner.Split(bufio.ScanLines) 26 | for scanner.Scan() { 27 | w.eventsAPI.sendLogEvent(w.eventType, scanner.Text()) 28 | } 29 | return len(logline), nil 30 | } 31 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/telemetry/eventLog.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package telemetry 5 | 6 | type EventLog struct { 7 | Events []SandboxEvent `json:"events,omitempty"` // populated by the StandaloneEventLog object 8 | Traces []TracingEvent `json:"traces,omitempty"` 9 | } 10 | 11 | func NewEventLog() *EventLog { 12 | return &EventLog{} 13 | } 14 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/telemetry/logs_egress_api.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package telemetry 5 | 6 | import "io" 7 | 8 | type StandaloneLogsEgressAPI struct { 9 | api *StandaloneEventsAPI 10 | } 11 | 12 | func NewStandaloneLogsEgressAPI(api *StandaloneEventsAPI) *StandaloneLogsEgressAPI { 13 | return &StandaloneLogsEgressAPI{ 14 | api: api, 15 | } 16 | } 17 | 18 | func (s *StandaloneLogsEgressAPI) GetExtensionSockets() (io.Writer, io.Writer, error) { 19 | w := NewSandboxAgentWriter(s.api, "extension") 20 | return w, w, nil 21 | } 22 | 23 | func (s *StandaloneLogsEgressAPI) GetRuntimeSockets() (io.Writer, io.Writer, error) { 24 | w := NewSandboxAgentWriter(s.api, "function") 25 | return w, w, nil 26 | } 27 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/telemetry/structured_logger.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package telemetry 5 | 6 | import ( 7 | "github.com/sirupsen/logrus" 8 | "os" 9 | ) 10 | 11 | var log = getLogger() 12 | 13 | func getLogger() *logrus.Logger { 14 | formatter := logrus.JSONFormatter{} 15 | formatter.DisableTimestamp = true 16 | logger := new(logrus.Logger) 17 | logger.Out = os.Stdout 18 | logger.Formatter = &formatter 19 | logger.Level = logrus.InfoLevel 20 | return logger 21 | } 22 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/util.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | 12 | log "github.com/sirupsen/logrus" 13 | "go.amzn.com/lambda/rapi/model" 14 | ) 15 | 16 | const ( 17 | DoneFailedHTTPCode = 502 18 | ) 19 | 20 | type ErrorType int 21 | 22 | const ( 23 | ClientInvalidRequest ErrorType = iota 24 | ) 25 | 26 | func (t ErrorType) String() string { 27 | switch t { 28 | case ClientInvalidRequest: 29 | return "Client.InvalidRequest" 30 | } 31 | return fmt.Sprintf("Cannot stringify standalone.ErrorType.%d", int(t)) 32 | } 33 | 34 | type ResponseWriterProxy struct { 35 | Body []byte 36 | StatusCode int 37 | header http.Header 38 | } 39 | 40 | func (w *ResponseWriterProxy) Header() http.Header { 41 | if w.header == nil { 42 | w.header = http.Header{} 43 | } 44 | return w.header 45 | } 46 | 47 | func (w *ResponseWriterProxy) Write(b []byte) (int, error) { 48 | w.Body = b 49 | return 0, nil 50 | } 51 | 52 | func (w *ResponseWriterProxy) WriteHeader(statusCode int) { 53 | w.StatusCode = statusCode 54 | } 55 | 56 | func (w *ResponseWriterProxy) IsError() bool { 57 | return w.StatusCode != 0 && w.StatusCode/100 != 2 58 | } 59 | 60 | func readBodyAndUnmarshalJSON(r *http.Request, dst interface{}) *ErrorReply { 61 | bodyBytes, err := io.ReadAll(r.Body) 62 | if err != nil { 63 | return newErrorReply(ClientInvalidRequest, fmt.Sprintf("Failed to read full body: %s", err)) 64 | } 65 | 66 | if err = json.Unmarshal(bodyBytes, dst); err != nil { 67 | return newErrorReply(ClientInvalidRequest, fmt.Sprintf("Invalid json %s: %s", string(bodyBytes), err)) 68 | } 69 | 70 | return nil 71 | } 72 | 73 | type ErrorReply struct { 74 | model.ErrorResponse 75 | } 76 | 77 | type RuntimeErrorReply struct { 78 | Payload []byte 79 | } 80 | 81 | func (e *RuntimeErrorReply) Send(w http.ResponseWriter, r *http.Request) { 82 | w.WriteHeader(500) 83 | w.Write(e.Payload) 84 | } 85 | 86 | func newErrorReply(errType ErrorType, errMsg string) *ErrorReply { 87 | return &ErrorReply{ErrorResponse: model.ErrorResponse{ErrorType: errType.String(), ErrorMessage: errMsg}} 88 | } 89 | 90 | func (e *ErrorReply) Send(w http.ResponseWriter, r *http.Request) { 91 | http.Error(w, e.ErrorType, 400) 92 | bodyJSON, err := json.Marshal(*e) 93 | if err != nil { 94 | http.Error(w, "Invalid format", 500) 95 | log.Errorf("Failed to Marshal(%#v): %s", e, err) 96 | } else { 97 | w.Write(bodyJSON) 98 | } 99 | } 100 | 101 | type SuccessReply struct { 102 | Body []byte 103 | } 104 | 105 | func (s *SuccessReply) Send(w http.ResponseWriter, r *http.Request) { 106 | w.Write(s.Body) 107 | } 108 | 109 | type FailureReply struct { 110 | Body []byte 111 | } 112 | 113 | func (s *FailureReply) Send(w http.ResponseWriter, r *http.Request) { 114 | w.WriteHeader(DoneFailedHTTPCode) 115 | w.Write(s.Body) 116 | } 117 | 118 | type Reply interface { 119 | Send(http.ResponseWriter, *http.Request) 120 | } 121 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/waitUntilInitializedHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "net/http" 8 | 9 | "go.amzn.com/lambda/rapidcore" 10 | ) 11 | 12 | func WaitUntilInitializedHandler(w http.ResponseWriter, r *http.Request, s InteropServer) { 13 | err := s.AwaitInitialized() 14 | if err != nil { 15 | switch err { 16 | case rapidcore.ErrInitDoneFailed: 17 | w.WriteHeader(DoneFailedHTTPCode) 18 | case rapidcore.ErrInitResetReceived: 19 | w.WriteHeader(DoneFailedHTTPCode) 20 | } 21 | } 22 | w.WriteHeader(http.StatusOK) 23 | } 24 | -------------------------------------------------------------------------------- /lambda/rapidcore/standalone/waitUntilReleaseHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package standalone 5 | 6 | import ( 7 | "net/http" 8 | 9 | "go.amzn.com/lambda/rapidcore" 10 | ) 11 | 12 | func WaitUntilReleaseHandler(w http.ResponseWriter, r *http.Request, s InteropServer) { 13 | releaseAwait, err := s.AwaitRelease() 14 | if err != nil { 15 | switch err { 16 | case rapidcore.ErrInvokeDoneFailed: 17 | w.WriteHeader(http.StatusBadGateway) 18 | case rapidcore.ErrReleaseReservationDone: 19 | // TODO return sandbox status when we implement async reset handling 20 | // TODO use http.StatusOK 21 | w.WriteHeader(http.StatusGatewayTimeout) 22 | return 23 | case rapidcore.ErrInitDoneFailed: 24 | w.WriteHeader(DoneFailedHTTPCode) 25 | w.Write(releaseAwait.AsJSON()) 26 | return 27 | } 28 | } 29 | 30 | w.Write(releaseAwait.AsJSON()) 31 | } 32 | -------------------------------------------------------------------------------- /lambda/supervisor/model/model_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package model 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // LockHard accepts deadlines encoded as RFC3339 - we enforce this with a test 13 | func Test_KillDeadlineIsMarshalledIntoRFC3339(t *testing.T) { 14 | deadline, err := time.Parse(time.RFC3339, "2022-12-21T10:00:00Z") 15 | if err != nil { 16 | t.Error(err) 17 | } 18 | k := KillRequest{ 19 | Name: "", 20 | Domain: "", 21 | Deadline: deadline, 22 | } 23 | bytes, err := json.Marshal(k) 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | exepected := `{"name":"","domain":"","deadline":"2022-12-21T10:00:00Z"}` 28 | if string(bytes) != exepected { 29 | t.Errorf("error in marshaling `KillRequest` it does not match the expected string (Expected(%q) != Got(%q))", exepected, string(bytes)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lambda/telemetry/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package telemetry 5 | 6 | import "errors" 7 | 8 | const ( 9 | // Metrics 10 | SubscribeSuccess = "logs_api_subscribe_success" 11 | SubscribeClientErr = "logs_api_subscribe_client_err" 12 | SubscribeServerErr = "logs_api_subscribe_server_err" 13 | NumSubscribers = "logs_api_num_subscribers" 14 | ) 15 | 16 | // ErrTelemetryServiceOff returned on attempt to subscribe after telemetry service has been turned off. 17 | var ErrTelemetryServiceOff = errors.New("ErrTelemetryServiceOff") 18 | -------------------------------------------------------------------------------- /lambda/telemetry/logs_egress_api.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package telemetry 5 | 6 | import ( 7 | "io" 8 | "os" 9 | ) 10 | 11 | // StdLogsEgressAPI is the interface that wraps the basic methods required to setup 12 | // logs channels for Runtime's stdout/stderr and Extension's stdout/stderr. 13 | // 14 | // Implementation should return a Writer implementor for stdout and another for 15 | // stderr on success and an error on failure. 16 | type StdLogsEgressAPI interface { 17 | GetExtensionSockets() (io.Writer, io.Writer, error) 18 | GetRuntimeSockets() (io.Writer, io.Writer, error) 19 | } 20 | 21 | type NoOpLogsEgressAPI struct{} 22 | 23 | func (s *NoOpLogsEgressAPI) GetExtensionSockets() (io.Writer, io.Writer, error) { 24 | // os.Stderr can not be used for the stderrWriter because stderr is for internal logging (not customer visible). 25 | return os.Stdout, os.Stdout, nil 26 | } 27 | 28 | func (s *NoOpLogsEgressAPI) GetRuntimeSockets() (io.Writer, io.Writer, error) { 29 | // os.Stderr can not be used for the stderrWriter because stderr is for internal logging (not customer visible). 30 | return os.Stdout, os.Stdout, nil 31 | } 32 | 33 | var _ StdLogsEgressAPI = (*NoOpLogsEgressAPI)(nil) 34 | -------------------------------------------------------------------------------- /lambda/telemetry/logs_subscription_api.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package telemetry 5 | 6 | import ( 7 | "io" 8 | "net/http" 9 | 10 | "go.amzn.com/lambda/interop" 11 | ) 12 | 13 | // SubscriptionAPI represents interface that implementations of Telemetry API have to satisfy to be RAPID-compatible 14 | type SubscriptionAPI interface { 15 | Subscribe(agentName string, body io.Reader, headers map[string][]string, remoteAddr string) (resp []byte, status int, respHeaders map[string][]string, err error) 16 | RecordCounterMetric(metricName string, count int) 17 | FlushMetrics() interop.TelemetrySubscriptionMetrics 18 | Clear() 19 | TurnOff() 20 | GetEndpointURL() string 21 | GetServiceClosedErrorMessage() string 22 | GetServiceClosedErrorType() string 23 | } 24 | 25 | type NoOpSubscriptionAPI struct{} 26 | 27 | // Subscribe writes response to a shared memory 28 | func (m *NoOpSubscriptionAPI) Subscribe(agentName string, body io.Reader, headers map[string][]string, remoteAddr string) ([]byte, int, map[string][]string, error) { 29 | return []byte(`{}`), http.StatusOK, map[string][]string{}, nil 30 | } 31 | 32 | func (m *NoOpSubscriptionAPI) RecordCounterMetric(metricName string, count int) {} 33 | 34 | func (m *NoOpSubscriptionAPI) FlushMetrics() interop.TelemetrySubscriptionMetrics { 35 | return interop.TelemetrySubscriptionMetrics(map[string]int{}) 36 | } 37 | 38 | func (m *NoOpSubscriptionAPI) Clear() {} 39 | 40 | func (m *NoOpSubscriptionAPI) TurnOff() {} 41 | 42 | func (m *NoOpSubscriptionAPI) GetEndpointURL() string { return "" } 43 | 44 | func (m *NoOpSubscriptionAPI) GetServiceClosedErrorMessage() string { return "" } 45 | 46 | func (m *NoOpSubscriptionAPI) GetServiceClosedErrorType() string { return "" } 47 | -------------------------------------------------------------------------------- /lambda/testdata/agents/bash_true.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | -------------------------------------------------------------------------------- /lambda/testdata/async_assertion_utils.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package testdata 5 | 6 | import ( 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func WaitForErrorWithTimeout(channel <-chan error, timeout time.Duration) error { 12 | select { 13 | case err := <-channel: 14 | return err 15 | case <-time.After(timeout): 16 | return nil 17 | } 18 | } 19 | 20 | func Eventually(t *testing.T, testFunc func() (bool, error), pollingIntervalMultiple time.Duration, retries int) bool { 21 | for try := 0; try < retries; try++ { 22 | success, err := testFunc() 23 | if success { 24 | return true 25 | } 26 | if err != nil { 27 | t.Logf("try %d: %v", try, err) 28 | } 29 | time.Sleep(time.Duration(try) * pollingIntervalMultiple) 30 | } 31 | return false 32 | } -------------------------------------------------------------------------------- /lambda/testdata/bash_function.sh: -------------------------------------------------------------------------------- 1 | function handler () { 2 | EVENT_DATA=$1 3 | echo "$EVENT_DATA" 1>&2; 4 | RESPONSE="Echoing request: '$EVENT_DATA'" 5 | 6 | echo $RESPONSE 7 | } -------------------------------------------------------------------------------- /lambda/testdata/bash_runtime.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -euo pipefail 4 | 5 | # Initialization - load function handler 6 | source $LAMBDA_TASK_ROOT/"bash_function.sh" 7 | 8 | # Processing 9 | while true 10 | do 11 | HEADERS="$(mktemp)" 12 | # Get an event 13 | EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") 14 | REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) 15 | 16 | # Execute the handler function from the script 17 | FN_PATH=$LAMBDA_TASK_ROOT/"bash_function.sh" 18 | RESPONSE=$($FN_PATH "$EVENT_DATA") 19 | 20 | # Send the response 21 | curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "response_from_runtime" 22 | done -------------------------------------------------------------------------------- /lambda/testdata/bash_script_with_child_proc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Spawn one child process recursively and spin 4 | # When parent process receives a SIGTERM, child process doesn't exit 5 | 6 | if [ -z "$DONT_SPAWN" ] 7 | then 8 | DONT_SPAWN=true ./$0 & 9 | fi 10 | 11 | while true 12 | do 13 | sleep 1 14 | done -------------------------------------------------------------------------------- /lambda/testdata/env_setup_helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package testdata 5 | 6 | import ( 7 | "net" 8 | ) 9 | 10 | // Test helpers 11 | type TestSocketsRapid struct { 12 | CtrlFd int 13 | CnslFd int 14 | } 15 | 16 | type TestSocketsSlicer struct { 17 | CtrlSock net.Conn 18 | CnslSock net.Conn 19 | CtrlFd int 20 | CnslFd int 21 | } 22 | -------------------------------------------------------------------------------- /lambda/testdata/mockcommand.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /* Package testdata holds ancillary data needed by 5 | the tests. The go tool ignores this directory. 6 | https://golang.org/pkg/cmd/go/internal/test/ */ 7 | package testdata 8 | 9 | import ( 10 | "context" 11 | ) 12 | 13 | // MockCommand represents a mock of os/exec.Cmd for testing 14 | type MockCommand struct { 15 | done chan error 16 | ctx context.Context 17 | } 18 | 19 | // NewMockCommand returns a MockCommand that satisfies 20 | // the command interface 21 | func NewMockCommand(ctx context.Context) MockCommand { 22 | done := make(chan error) 23 | return MockCommand{done, ctx} 24 | } 25 | 26 | // Start represents a successful cmd.Start() without 27 | // errors 28 | func (c MockCommand) Start() error { 29 | return nil 30 | } 31 | 32 | // Wait represents a cancelable call to cmd.Wait() 33 | func (c MockCommand) Wait() error { 34 | select { 35 | case <-c.done: 36 | return nil 37 | case <-c.ctx.Done(): 38 | return c.ctx.Err() 39 | } 40 | } 41 | 42 | // ForceExit tells goroutine blocking on Wait() to exit 43 | func (c MockCommand) ForceExit() { 44 | c.done <- nil 45 | } 46 | -------------------------------------------------------------------------------- /lambda/testdata/mockthread/mockthread.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package mockthread 5 | 6 | // MockManagedThread implements core.Suspendable interface but 7 | // does not suspend running thread on condition. 8 | type MockManagedThread struct{} 9 | 10 | // SuspendUnsafe does not suspend running thread. 11 | func (s *MockManagedThread) SuspendUnsafe() {} 12 | 13 | // Release resumes suspended thread. 14 | func (s *MockManagedThread) Release() {} 15 | 16 | // Lock: no-op 17 | func (s *MockManagedThread) Lock() {} 18 | 19 | // Unlock: no-op 20 | func (s *MockManagedThread) Unlock() {} 21 | -------------------------------------------------------------------------------- /lambda/testdata/mocktracer/mocktracer.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package mocktracer 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "go.amzn.com/lambda/xray" 11 | ) 12 | 13 | // MockStartTime is start time set in Start method 14 | var MockStartTime = time.Now().UnixNano() 15 | 16 | // MockEndTime is end time set in End method 17 | var MockEndTime = time.Now().UnixNano() + 1 18 | 19 | // MockTracer is used for unit tests 20 | type MockTracer struct { 21 | documentsMap map[xray.DocumentKey]xray.Document 22 | sentDocuments []xray.Document 23 | } 24 | 25 | // Send will add document name to sentDocuments list 26 | func (m *MockTracer) Send(document xray.Document) (dk xray.DocumentKey, err error) { 27 | if len(document.ID) == 0 { 28 | // Give it a predictable ID that we could use in our assertions. 29 | document.ID = IDFor(document.Name) 30 | } 31 | m.sentDocuments = append(m.sentDocuments, document) 32 | return xray.DocumentKey{ 33 | TraceID: document.TraceID, 34 | DocumentID: document.ID, 35 | }, nil 36 | } 37 | 38 | // Start will save document in documentsMap 39 | func (m *MockTracer) Start(document xray.Document) (dk xray.DocumentKey, err error) { 40 | document.StartTime = float64(MockStartTime) / xray.TimeDenominator 41 | document.InProgress = true 42 | dk, err = m.Send(document) 43 | m.documentsMap[dk] = document 44 | return 45 | } 46 | 47 | // SetOptions will set value of a field on a saved document 48 | func (m *MockTracer) SetOptions(dk xray.DocumentKey, documentOptions ...xray.DocumentOption) (err error) { 49 | document := m.documentsMap[dk] 50 | 51 | for _, fieldValueSetter := range documentOptions { 52 | fieldValueSetter(&document) 53 | } 54 | 55 | m.documentsMap[dk] = document 56 | 57 | return nil 58 | } 59 | 60 | // End will delete the key-value pair in documentsMap 61 | func (m *MockTracer) End(dk xray.DocumentKey) (err error) { 62 | document := m.documentsMap[dk] 63 | document.EndTime = float64(MockEndTime) / xray.TimeDenominator 64 | document.InProgress = false 65 | 66 | m.Send(document) 67 | delete(m.documentsMap, dk) 68 | return 69 | } 70 | 71 | // GetSentDocuments will return sentDocuments for unit test to verify 72 | func (m *MockTracer) GetSentDocuments() []xray.Document { 73 | return m.sentDocuments 74 | } 75 | 76 | // ResetSentDocuments resets captured documents list to an empty list. 77 | func (m *MockTracer) ResetSentDocuments() { 78 | m.sentDocuments = []xray.Document{} 79 | } 80 | 81 | // SetDocumentMap sets internal state. 82 | func (m *MockTracer) SetDocumentMap(dm map[xray.DocumentKey]xray.Document) { 83 | m.documentsMap = dm 84 | } 85 | 86 | // Capture mock method for capturing segments. 87 | func (m *MockTracer) Capture(ctx context.Context, document xray.Document, criticalFunction func(context.Context) error) error { 88 | return nil 89 | } 90 | 91 | // SetOptionsCtx contextual SetOptions. 92 | func (m *MockTracer) SetOptionsCtx(ctx context.Context, documentOptions ...xray.DocumentOption) (err error) { 93 | return nil 94 | } 95 | 96 | // NewMockTracer is the constructor for mock tracer 97 | func NewMockTracer() xray.Tracer { 98 | return &MockTracer{ 99 | documentsMap: make(map[xray.DocumentKey]xray.Document), 100 | sentDocuments: []xray.Document{}, 101 | } 102 | } 103 | 104 | // IDFor constructs a predictable id for a given name. 105 | func IDFor(name string) string { 106 | return name + "_SEGMID" 107 | } 108 | -------------------------------------------------------------------------------- /lambda/testdata/parametrization.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package testdata 5 | 6 | // SuppressInitTests is a parametrization vector for testing suppress init behavior. 7 | var SuppressInitTests = []struct { 8 | TestName string 9 | SuppressInit bool 10 | }{ 11 | {"Unsuppressed", false}, 12 | {"Suppressed", true}, 13 | } 14 | -------------------------------------------------------------------------------- /test/integration/testdata/Dockerfile-allinone: -------------------------------------------------------------------------------- 1 | ARG IMAGE_ARCH 2 | FROM public.ecr.aws/lambda/python:3.12-$IMAGE_ARCH 3 | 4 | WORKDIR /var/task 5 | COPY ./ ./ 6 | 7 | # This is to verify env vars are parsed correctly before executing the function 8 | ENV MyEnv="4=4" 9 | -------------------------------------------------------------------------------- /test/integration/testdata/main.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import time 5 | import os 6 | 7 | def sleep_handler(event, context): 8 | time.sleep(5) 9 | return "I was sleeping" 10 | 11 | 12 | def exception_handler(event, context): 13 | raise Exception("Raising an exception") 14 | 15 | 16 | def success_handler(event, context): 17 | print("Printing data to console") 18 | 19 | return "My lambda ran succesfully" 20 | 21 | 22 | def check_env_var_handler(event, context): 23 | return os.environ.get("MyEnv") 24 | 25 | 26 | def assert_env_var_is_overwritten(event, context): 27 | print(os.environ.get("AWS_LAMBDA_FUNCTION_NAME")) 28 | if os.environ.get("AWS_LAMBDA_FUNCTION_NAME") == "test_function": 29 | raise("Function name was not overwritten") 30 | else: 31 | return "My lambda ran succesfully" 32 | 33 | def assert_lambda_arn_in_context(event, context): 34 | if context.invoked_function_arn == f"arn:aws:lambda:us-east-1:012345678912:function:{os.environ.get('AWS_LAMBDA_FUNCTION_NAME', 'test_function')}": 35 | return "My lambda ran succesfully" 36 | else: 37 | raise("Function Arn was not there") 38 | 39 | 40 | def check_remaining_time_handler(event, context): 41 | # Wait 1s to see if the remaining time changes 42 | time.sleep(1) 43 | return context.get_remaining_time_in_millis() 44 | 45 | 46 | def custom_client_context_handler(event, context): 47 | return context.client_context.custom 48 | --------------------------------------------------------------------------------