Skip to content

Commit cc08601

Browse files
committed
Basic Working Version
1 parent d776f4d commit cc08601

File tree

14 files changed

+458
-0
lines changed

14 files changed

+458
-0
lines changed

.github/workflows/simulator.yml

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Run Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
15+
env:
16+
LATEST_GO_VERSION: 1.19
17+
18+
jobs:
19+
test:
20+
strategy:
21+
matrix:
22+
os: [ubuntu-latest, macos-latest, windows-latest]
23+
go: [1.19]
24+
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
25+
runs-on: ${{ matrix.os }}
26+
steps:
27+
- name: Checkout Code
28+
uses: actions/checkout@v3
29+
30+
- name: Set up Go ${{ matrix.go }}
31+
uses: actions/setup-go@v3
32+
with:
33+
go-version: ${{ matrix.go }}
34+
35+
- name: Run Tests
36+
run: go test -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
37+
38+
benchmark:
39+
needs: test
40+
name: Benchmark comparison
41+
runs-on: ubuntu-latest
42+
steps:
43+
- name: Checkout Code (Previous)
44+
uses: actions/checkout@v3
45+
with:
46+
ref: ${{ github.base_ref }}
47+
path: previous
48+
49+
- name: Checkout Code (New)
50+
uses: actions/checkout@v3
51+
with:
52+
path: new
53+
54+
- name: Set up Go ${{ matrix.go }}
55+
uses: actions/setup-go@v3
56+
with:
57+
go-version: ${{ env.LATEST_GO_VERSION }}
58+
59+
- name: Install Dependencies
60+
run: go install golang.org/x/perf/cmd/benchstat@latest
61+
62+
- name: Run Benchmark (Previous)
63+
run: |
64+
cd previous
65+
go test -run="-" -bench=".*" -count=8 ./... > benchmark.txt
66+
- name: Run Benchmark (New)
67+
run: |
68+
cd new
69+
go test -run="-" -bench=".*" -count=8 ./... > benchmark.txt
70+
- name: Run Benchstat
71+
run: |
72+
benchstat previous/benchmark.txt new/benchmark.txt

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
.idea/

cmd/main.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"lambda-runtime-simulator/pkg/config"
6+
"lambda-runtime-simulator/pkg/controller"
7+
"lambda-runtime-simulator/pkg/event"
8+
"lambda-runtime-simulator/pkg/server"
9+
"log"
10+
"net/http"
11+
12+
"github.com/kelseyhightower/envconfig"
13+
"github.com/labstack/echo/v4"
14+
)
15+
16+
type EnvironmentVariables struct {
17+
Port int `envconfig:"port" default:"8080"`
18+
LambdaTimeout int `envconfig:"function_timeout" default:"120"`
19+
Arn string `envconfig:"lambda_arn"`
20+
}
21+
22+
func main() {
23+
var env EnvironmentVariables
24+
err := envconfig.Process("", &env)
25+
if err != nil {
26+
panic(err)
27+
}
28+
29+
e := echo.New()
30+
31+
controllers := setupControllers(&env)
32+
33+
server.SetupServer(e, controllers...)
34+
35+
if err := e.Start(fmt.Sprint("0.0.0.0:", env.Port)); err != http.ErrServerClosed {
36+
log.Fatal(err)
37+
}
38+
}
39+
40+
func setupControllers(env *EnvironmentVariables) []controller.Controller {
41+
var result []controller.Controller
42+
43+
cfg := config.Runtime{
44+
Port: env.Port,
45+
TimeoutInSeconds: env.LambdaTimeout,
46+
Arn: env.Arn,
47+
}
48+
49+
svc := event.NewService(&cfg)
50+
51+
runtimeController := controller.NewRuntimeController(cfg, svc)
52+
result = append(result, runtimeController)
53+
54+
adminController := controller.NewAdminController(svc)
55+
result = append(result, adminController)
56+
57+
return result
58+
}

go.mod

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module lambda-runtime-simulator
2+
3+
go 1.19
4+
5+
require (
6+
github.com/google/uuid v1.3.0
7+
github.com/kelseyhightower/envconfig v1.4.0
8+
github.com/labstack/echo/v4 v4.10.0
9+
)
10+
11+
require (
12+
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
13+
github.com/labstack/gommon v0.4.0 // indirect
14+
github.com/mattn/go-colorable v0.1.13 // indirect
15+
github.com/mattn/go-isatty v0.0.17 // indirect
16+
github.com/valyala/bytebufferpool v1.0.0 // indirect
17+
github.com/valyala/fasttemplate v1.2.2 // indirect
18+
golang.org/x/crypto v0.5.0 // indirect
19+
golang.org/x/net v0.5.0 // indirect
20+
golang.org/x/sys v0.4.0 // indirect
21+
golang.org/x/text v0.6.0 // indirect
22+
golang.org/x/time v0.3.0 // indirect
23+
)

lambda-runtime-simulator.iml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<module type="WEB_MODULE" version="4">
3+
<component name="Go" enabled="true" />
4+
<component name="NewModuleRootManager" inherit-compiler-output="true">
5+
<exclude-output />
6+
<content url="file://$MODULE_DIR$" />
7+
<orderEntry type="sourceFolder" forTests="false" />
8+
</component>
9+
</module>

pkg/config/model.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package config
2+
3+
const (
4+
PushApiPort = "PUSH_API_PORT"
5+
RuntimeApiPort = "RUNTIME_API_PORT"
6+
EnvVariableFunctionArn = "FUNCTION_ARN"
7+
)
8+
9+
type Runtime struct {
10+
Port int
11+
TimeoutInSeconds int
12+
Arn string
13+
}

pkg/controller/admin.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package controller
2+
3+
import (
4+
"encoding/json"
5+
"github.com/labstack/echo/v4"
6+
"io"
7+
"lambda-runtime-simulator/pkg/event"
8+
"net/http"
9+
)
10+
11+
type AdminController struct {
12+
service *event.Service
13+
}
14+
15+
var _ Controller = &AdminController{}
16+
17+
func NewAdminController(svc *event.Service) *AdminController {
18+
return &AdminController{
19+
service: svc,
20+
}
21+
}
22+
23+
func (a AdminController) RegisterRoutes(e *echo.Echo) error {
24+
e.GET("/log", nil)
25+
e.GET("/log/stream", nil)
26+
e.GET("/response/:requestId", nil)
27+
e.POST("/log/clear", nil)
28+
e.POST("/push", a.Push)
29+
30+
return nil
31+
}
32+
33+
func (a AdminController) Push(c echo.Context) error {
34+
body, err := io.ReadAll(c.Request().Body)
35+
if err != nil {
36+
return echo.NewHTTPError(http.StatusBadRequest, err)
37+
}
38+
39+
// Json sanity check
40+
var data interface{}
41+
err = json.Unmarshal(body, &data)
42+
if err != nil {
43+
return echo.NewHTTPError(http.StatusBadRequest, err)
44+
}
45+
46+
err = a.service.PushInvocation(string(body))
47+
if err != nil {
48+
return echo.NewHTTPError(http.StatusBadRequest, err)
49+
}
50+
51+
return nil
52+
}

pkg/controller/dto.go

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package controller

pkg/controller/model.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package controller
2+
3+
import "github.com/labstack/echo/v4"
4+
5+
type Controller interface {
6+
RegisterRoutes(e *echo.Echo) error
7+
}

pkg/controller/runtime.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package controller
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"lambda-runtime-simulator/pkg/config"
7+
"lambda-runtime-simulator/pkg/event"
8+
"lambda-runtime-simulator/pkg/lambda"
9+
"net/http"
10+
11+
"github.com/labstack/echo/v4"
12+
)
13+
14+
type RuntimeController struct {
15+
config config.Runtime
16+
service *event.Service
17+
}
18+
19+
var _ Controller = &RuntimeController{}
20+
21+
func NewRuntimeController(cfg config.Runtime, service *event.Service) *RuntimeController {
22+
return &RuntimeController{
23+
config: cfg,
24+
service: service,
25+
}
26+
}
27+
28+
func (r RuntimeController) RegisterRoutes(e *echo.Echo) error {
29+
e.GET("/2018-06-01/runtime/invocation/next", r.NextInvocation)
30+
e.POST("/2018-06-01/runtime/invocation/:requestId/response", r.SendResponse)
31+
e.POST("/2018-06-01/runtime/invocation/:requestId/error", r.SendError)
32+
33+
return nil
34+
}
35+
36+
func (r RuntimeController) NextInvocation(c echo.Context) error {
37+
next, err := r.service.GetNextInvocation()
38+
if err != nil {
39+
return err
40+
}
41+
42+
c.Response().Header().Set(lambda.HeaderLambdaRuntimeAwsRequestId, next.Id)
43+
c.Response().Header().Set(lambda.HeaderLambdaRuntimeInvokedFunctionArn, r.config.Arn)
44+
c.Response().Header().Set(lambda.HeaderLambdaRuntimeDeadlineMs, fmt.Sprint(next.Timeout.Unix()))
45+
// TODO: Implement Tracing Part
46+
return c.JSON(http.StatusOK, next.Body)
47+
}
48+
49+
func (r RuntimeController) SendResponse(c echo.Context) error {
50+
requestId := c.Param("requestId")
51+
52+
body, err := io.ReadAll(c.Request().Body)
53+
if err != nil {
54+
return err
55+
}
56+
57+
err = r.service.SendResponse(requestId, body)
58+
if err != nil {
59+
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
60+
}
61+
62+
return nil
63+
}
64+
65+
func (r RuntimeController) SendError(c echo.Context) error {
66+
errorType := c.Request().Header.Get(lambda.HeaderLambdaRuntimeFunctionErrorType)
67+
requestId := c.Param("requestId")
68+
69+
var body event.RuntimeError
70+
err := c.Bind(&body)
71+
if err != nil {
72+
return c.String(http.StatusBadRequest, "invalid body")
73+
}
74+
75+
err = r.service.SendError(requestId, &body, errorType)
76+
if err != nil {
77+
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
78+
}
79+
80+
return err
81+
}

pkg/event/model.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package event
2+
3+
type RuntimeError struct {
4+
ErrorMessage string `json:"errorMessage"`
5+
ErrorType string `json:"errorType"`
6+
StackTrace []string `json:"stackTrace,omitempty"`
7+
}

0 commit comments

Comments
 (0)