Skip to content

Commit abd363e

Browse files
valerenaJJC1138
andauthored
Merge from aws-lambda-runtime-interface-emulator/develop (#40)
* Pull upstream changes (#34) * Pull upstream changes 2021/06 (#39) * docs: Fix ordering of options in example (#36) Co-authored-by: Jon Colverson <[email protected]>
1 parent 00d793b commit abd363e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1714
-316
lines changed

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,11 @@ You install the runtime interface emulator to your local machine. When you run t
9999

100100
2. Run your Lambda image function using the docker run command.
101101

102-
`docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 myfunction:latest
103-
--entrypoint /aws-lambda/aws-lambda-rie <image entrypoint> <(optional) image command>`
102+
```
103+
docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
104+
--entrypoint /aws-lambda/aws-lambda-rie \
105+
myfunction:latest <image entrypoint> <(optional) image command>
106+
````
104107
105108
This runs the image as a container and starts up an endpoint locally at `localhost:9000/2015-03-31/functions/function/invocations`.
106109

cmd/aws-lambda-rie/handlers.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
package main
55

66
import (
7+
"bytes"
78
"fmt"
8-
"io"
99
"io/ioutil"
1010
"math"
1111
"net/http"
@@ -24,7 +24,7 @@ import (
2424

2525
type Sandbox interface {
2626
Init(i *interop.Init, invokeTimeoutMs int64)
27-
Invoke(responseWriter io.Writer, invoke *interop.Invoke) error
27+
Invoke(responseWriter http.ResponseWriter, invoke *interop.Invoke) error
2828
}
2929

3030
var initDone bool
@@ -98,7 +98,7 @@ func InvokeHandler(w http.ResponseWriter, r *http.Request, sandbox Sandbox) {
9898
InvokedFunctionArn: fmt.Sprintf("arn:aws:lambda:us-east-1:012345678912:function:%s", GetenvWithDefault("AWS_LAMBDA_FUNCTION_NAME", "test_function")),
9999
TraceID: r.Header.Get("X-Amzn-Trace-Id"),
100100
LambdaSegmentID: r.Header.Get("X-Amzn-Segment-Id"),
101-
Payload: bodyBytes,
101+
Payload: bytes.NewReader(bodyBytes),
102102
CorrelationID: "invokeCorrelationID",
103103
}
104104
fmt.Println("START RequestId: " + invokePayload.ID + " Version: " + functionVersion)

cmd/aws-lambda-rie/main.go

+7-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
log "github.com/sirupsen/logrus"
1616
)
1717

18-
1918
const (
2019
optBootstrap = "/opt/bootstrap"
2120
runtimeBootstrap = "/var/runtime/bootstrap"
@@ -58,24 +57,22 @@ func getCLIArgs() (options, []string) {
5857
}
5958

6059
func getBootstrap(args []string, opts options) (*rapidcore.Bootstrap, string) {
61-
var bootstrapLookupCmdList [][]string
60+
var bootstrapLookupCmd []string
6261
var handler string
6362
currentWorkingDir := "/var/task" // default value
6463

6564
if len(args) <= 1 {
66-
bootstrapLookupCmdList = [][]string{
67-
[]string{fmt.Sprintf("%s/bootstrap", currentWorkingDir)},
68-
[]string{optBootstrap},
69-
[]string{runtimeBootstrap},
65+
bootstrapLookupCmd = []string{
66+
fmt.Sprintf("%s/bootstrap", currentWorkingDir),
67+
optBootstrap,
68+
runtimeBootstrap,
7069
}
7170

7271
// handler is used later to set an env var for Lambda Image support
7372
handler = ""
7473
} else if len(args) > 1 {
7574

76-
bootstrapLookupCmdList = [][]string{
77-
args[1:],
78-
}
75+
bootstrapLookupCmd = args[1:]
7976

8077
if cwd, err := os.Getwd(); err == nil {
8178
currentWorkingDir = cwd
@@ -92,5 +89,5 @@ func getBootstrap(args []string, opts options) (*rapidcore.Bootstrap, string) {
9289
log.Panic("insufficient arguments: bootstrap not provided")
9390
}
9491

95-
return rapidcore.NewBootstrap(bootstrapLookupCmdList, currentWorkingDir), handler
92+
return rapidcore.NewBootstrapSingleCmd(bootstrapLookupCmd, currentWorkingDir), handler
9693
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
}
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
"fmt"
8+
"io"
9+
"net/http"
10+
11+
"github.com/go-chi/chi"
12+
"go.amzn.com/lambda/interop"
13+
"go.amzn.com/lambda/metering"
14+
)
15+
16+
const (
17+
InvokeIDHeader = "Invoke-Id"
18+
InvokedFunctionArnHeader = "Invoked-Function-Arn"
19+
VersionIDHeader = "Invoked-Function-Version"
20+
ReservationTokenHeader = "Reservation-Token"
21+
CustomerHeadersHeader = "Customer-Headers"
22+
ContentTypeHeader = "Content-Type"
23+
24+
ErrorTypeHeader = "Error-Type"
25+
26+
EndOfResponseTrailer = "End-Of-Response"
27+
28+
SandboxErrorType = "Error.Sandbox"
29+
)
30+
31+
const (
32+
EndOfResponseComplete = "Complete"
33+
EndOfResponseTruncated = "Truncated"
34+
EndOfResponseOversized = "Oversized"
35+
)
36+
37+
var MaxDirectResponseSize int64 = interop.MaxPayloadSize // this is intentionally not a constant so we can configure it via CLI
38+
39+
func renderBadRequest(w http.ResponseWriter, r *http.Request, errorType string) {
40+
w.Header().Set(ErrorTypeHeader, errorType)
41+
w.WriteHeader(http.StatusBadRequest)
42+
w.Header().Set(EndOfResponseTrailer, EndOfResponseComplete)
43+
}
44+
45+
// ReceiveDirectInvoke parses invoke and verifies it against Token message. Uses deadline provided by Token
46+
// Renders BadRequest in case of error
47+
func ReceiveDirectInvoke(w http.ResponseWriter, r *http.Request, token interop.Token) (*interop.Invoke, error) {
48+
w.Header().Set("Trailer", EndOfResponseTrailer)
49+
50+
custHeaders := CustomerHeaders{}
51+
if err := custHeaders.Load(r.Header.Get(CustomerHeadersHeader)); err != nil {
52+
renderBadRequest(w, r, interop.ErrMalformedCustomerHeaders.Error())
53+
return nil, interop.ErrMalformedCustomerHeaders
54+
}
55+
56+
now := metering.Monotime()
57+
inv := &interop.Invoke{
58+
ID: r.Header.Get(InvokeIDHeader),
59+
ReservationToken: chi.URLParam(r, "reservationtoken"),
60+
InvokedFunctionArn: r.Header.Get(InvokedFunctionArnHeader),
61+
VersionID: r.Header.Get(VersionIDHeader),
62+
ContentType: r.Header.Get(ContentTypeHeader),
63+
CognitoIdentityID: custHeaders.CognitoIdentityID,
64+
CognitoIdentityPoolID: custHeaders.CognitoIdentityPoolID,
65+
TraceID: token.TraceID,
66+
LambdaSegmentID: token.LambdaSegmentID,
67+
ClientContext: custHeaders.ClientContext,
68+
Payload: r.Body,
69+
CorrelationID: "invokeCorrelationID",
70+
DeadlineNs: fmt.Sprintf("%d", now+token.FunctionTimeout.Nanoseconds()),
71+
NeedDebugLogs: token.NeedDebugLogs,
72+
InvokeReceivedTime: now,
73+
}
74+
75+
if inv.ID != token.InvokeID {
76+
renderBadRequest(w, r, interop.ErrInvalidInvokeID.Error())
77+
return nil, interop.ErrInvalidInvokeID
78+
}
79+
80+
if inv.ReservationToken != token.ReservationToken {
81+
renderBadRequest(w, r, interop.ErrInvalidReservationToken.Error())
82+
return nil, interop.ErrInvalidReservationToken
83+
}
84+
85+
if inv.VersionID != token.VersionID {
86+
renderBadRequest(w, r, interop.ErrInvalidFunctionVersion.Error())
87+
return nil, interop.ErrInvalidFunctionVersion
88+
}
89+
90+
if now > token.InvackDeadlineNs {
91+
renderBadRequest(w, r, interop.ErrReservationExpired.Error())
92+
return nil, interop.ErrReservationExpired
93+
}
94+
95+
w.Header().Set(VersionIDHeader, token.VersionID)
96+
w.Header().Set(ReservationTokenHeader, token.ReservationToken)
97+
w.Header().Set(InvokeIDHeader, token.InvokeID)
98+
99+
return inv, nil
100+
}
101+
102+
func SendDirectInvokeResponse(additionalHeaders map[string]string, payload io.Reader, w http.ResponseWriter) error {
103+
for k, v := range additionalHeaders {
104+
w.Header().Add(k, v)
105+
}
106+
107+
n, err := io.Copy(w, io.LimitReader(payload, MaxDirectResponseSize+1)) // +1 because we do allow 10MB but not 10MB + 1 byte
108+
if err != nil {
109+
w.Header().Set(EndOfResponseTrailer, EndOfResponseTruncated)
110+
} else if n == MaxDirectResponseSize+1 {
111+
w.Header().Set(EndOfResponseTrailer, EndOfResponseOversized)
112+
} else {
113+
w.Header().Set(EndOfResponseTrailer, EndOfResponseComplete)
114+
}
115+
return err
116+
}

lambda/core/registrations.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ func (s *registrationServiceImpl) getInternalStateDescription(appCtx appctx.Appl
158158
}
159159

160160
func (s *registrationServiceImpl) CountAgents() int {
161+
s.mutex.Lock()
162+
defer s.mutex.Unlock()
163+
164+
return s.countAgentsUnsafe()
165+
}
166+
167+
func (s *registrationServiceImpl) countAgentsUnsafe() int {
161168
res := 0
162169
s.externalAgents.Visit(func(a *ExternalAgent) {
163170
res++
@@ -237,7 +244,7 @@ func (s *registrationServiceImpl) CreateInternalAgent(agentName string) (*Intern
237244
return nil, ErrRegistrationServiceOff
238245
}
239246

240-
if s.CountAgents() >= MaxAgentsAllowed {
247+
if s.countAgentsUnsafe() >= MaxAgentsAllowed {
241248
return nil, ErrTooManyExtensions
242249
}
243250

lambda/core/statejson/description.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import (
1010

1111
// StateDescription ...
1212
type StateDescription struct {
13-
Name string `json:"name"`
14-
LastModified int64 `json:"lastModified"`
13+
Name string `json:"name"`
14+
LastModified int64 `json:"lastModified"`
15+
ResponseTimeNs int64 `json:"responseTimeNs"`
1516
}
1617

1718
// RuntimeDescription ...
@@ -34,10 +35,23 @@ type InternalStateDescription struct {
3435
FirstFatalError string `json:"firstFatalError"`
3536
}
3637

38+
// ResetDescription describes fields of the response to an INVOKE API request
39+
type ResetDescription struct {
40+
ExtensionsResetMs int64 `json:"extensionsResetMs"`
41+
}
42+
3743
func (s *InternalStateDescription) AsJSON() []byte {
3844
bytes, err := json.Marshal(s)
3945
if err != nil {
4046
log.Panicf("Failed to marshall internal states: %s", err)
4147
}
4248
return bytes
4349
}
50+
51+
func (s *ResetDescription) AsJSON() []byte {
52+
bytes, err := json.Marshal(s)
53+
if err != nil {
54+
log.Panicf("Failed to marshall reset description: %s", err)
55+
}
56+
return bytes
57+
}

lambda/core/states.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ type Runtime struct {
8585
currentState RuntimeState
8686
stateLastModified time.Time
8787
Pid int
88+
responseTime time.Time
8889

8990
RuntimeStartedState RuntimeState
9091
RuntimeInitErrorState RuntimeState
@@ -150,19 +151,27 @@ func (s *Runtime) InitError() error {
150151
func (s *Runtime) ResponseSent() error {
151152
s.ManagedThread.Lock()
152153
defer s.ManagedThread.Unlock()
153-
return s.currentState.ResponseSent()
154+
err := s.currentState.ResponseSent()
155+
if err == nil {
156+
s.responseTime = time.Now()
157+
}
158+
return err
154159
}
155160

156161
// GetRuntimeDescription returns runtime description object for debugging purposes
157162
func (s *Runtime) GetRuntimeDescription() statejson.RuntimeDescription {
158163
s.ManagedThread.Lock()
159164
defer s.ManagedThread.Unlock()
160-
return statejson.RuntimeDescription{
165+
res := statejson.RuntimeDescription{
161166
State: statejson.StateDescription{
162167
Name: s.currentState.Name(),
163168
LastModified: s.stateLastModified.UnixNano() / int64(time.Millisecond),
164169
},
165170
}
171+
if !s.responseTime.IsZero() {
172+
res.State.ResponseTimeNs = s.responseTime.UnixNano()
173+
}
174+
return res
166175
}
167176

168177
// NewRuntime returns new Runtime instance.

lambda/core/states_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ func TestRuntimeStateTransitionsFromInvocationResponseState(t *testing.T) {
110110
runtime.SetState(runtime.RuntimeInvocationResponseState)
111111
assert.NoError(t, runtime.ResponseSent())
112112
assert.Equal(t, runtime.RuntimeResponseSentState, runtime.GetState())
113+
assert.NotEqual(t, 0, runtime.GetRuntimeDescription().State.ResponseTimeNs)
113114
// InvocationResponse-> InvocationResponse
114115
runtime.SetState(runtime.RuntimeInvocationResponseState)
115116
assert.Equal(t, ErrNotAllowed, runtime.InvocationResponse())

lambda/fatalerror/fatalerror.go

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
AgentLaunchError ErrorType = "Extension.LaunchError" // agent could not be launched
1717
RuntimeExit ErrorType = "Runtime.ExitError"
1818
InvalidEntrypoint ErrorType = "Runtime.InvalidEntrypoint"
19+
InvalidWorkingDir ErrorType = "Runtime.InvalidWorkingDir"
1920
InvalidTaskConfig ErrorType = "Runtime.InvalidTaskConfig"
2021
Unknown ErrorType = "Unknown"
2122
)

0 commit comments

Comments
 (0)