Skip to content

Commit 2e652c4

Browse files
committed
Pulling upstream changes 2021-11
1 parent 3567179 commit 2e652c4

Some content is hidden

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

51 files changed

+1429
-364
lines changed

cmd/aws-lambda-rie/main.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ const (
2121
)
2222

2323
type options struct {
24-
LogLevel string `long:"log-level" default:"info" description:"log level"`
24+
LogLevel string `long:"log-level" default:"info" description:"log level"`
25+
InitCachingEnabled bool `long:"enable-init-caching" description:"Enable support for Init Caching"`
2526
}
2627

2728
func main() {
@@ -32,7 +33,11 @@ func main() {
3233
rapidcore.SetLogLevel(opts.LogLevel)
3334

3435
bootstrap, handler := getBootstrap(args, opts)
35-
sandbox := rapidcore.NewSandboxBuilder(bootstrap).AddShutdownFunc(context.CancelFunc(func() { os.Exit(0) })).SetExtensionsFlag(true)
36+
sandbox := rapidcore.
37+
NewSandboxBuilder(bootstrap).
38+
AddShutdownFunc(context.CancelFunc(func() { os.Exit(0) })).
39+
SetExtensionsFlag(true).
40+
SetInitCachingFlag(opts.InitCachingEnabled)
3641

3742
if len(handler) > 0 {
3843
sandbox.SetHandler(handler)
@@ -72,7 +77,7 @@ func getBootstrap(args []string, opts options) (*rapidcore.Bootstrap, string) {
7277
fmt.Sprintf("%s/bootstrap", currentWorkingDir),
7378
}
7479

75-
if !isBootstrapFileExist(bootstrapLookupCmd[0]) {
80+
if !isBootstrapFileExist(bootstrapLookupCmd[0]) {
7681
var bootstrapCmdCandidates = []string{
7782
optBootstrap,
7883
runtimeBootstrap,

lambda/agents/agent.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@ type ExternalAgentProcess struct {
2525
}
2626

2727
// NewExternalAgentProcess returns a new external agent process
28-
func NewExternalAgentProcess(path string, env []string, logWriter io.Writer) ExternalAgentProcess {
28+
func NewExternalAgentProcess(path string, env []string, stdoutWriter io.Writer, stderrWriter io.Writer) ExternalAgentProcess {
2929
command := exec.Command(path)
3030
command.Env = env
3131

32-
w := NewNewlineSplitWriter(logWriter)
33-
command.Stdout = w
34-
command.Stderr = w
32+
command.Stdout = NewNewlineSplitWriter(stdoutWriter)
33+
command.Stderr = NewNewlineSplitWriter(stderrWriter)
3534
command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
3635

3736
return ExternalAgentProcess{

lambda/agents/agent_test.go

+54-8
Original file line numberDiff line numberDiff line change
@@ -204,30 +204,76 @@ func TestFindAgentMixed(t *testing.T) {
204204
// Test our ability to start agents
205205
func TestAgentStart(t *testing.T) {
206206
assert := assert.New(t)
207-
agent := NewExternalAgentProcess("../testdata/agents/bash_true.sh", []string{}, &mockWriter{})
207+
agent := NewExternalAgentProcess("../testdata/agents/bash_true.sh", []string{}, &mockWriter{}, &mockWriter{})
208208
assert.Nil(agent.Start())
209209
assert.Nil(agent.Wait())
210210
}
211211

212212
// Test that execution of invalid agents is correctly reported
213213
func TestInvalidAgentStart(t *testing.T) {
214214
assert := assert.New(t)
215-
agent := NewExternalAgentProcess("/bin/none", []string{}, &mockWriter{})
215+
agent := NewExternalAgentProcess("/bin/none", []string{}, &mockWriter{}, &mockWriter{})
216216
assert.True(os.IsNotExist(agent.Start()))
217217
}
218218

219-
// Test that execution of invalid agents is correctly reported
220-
func TestAgentTelemetry(t *testing.T) {
219+
func TestAgentStdoutWriter(t *testing.T) {
220+
// Given
221+
assert := assert.New(t)
222+
223+
stdout := &mockWriter{}
224+
stderr := &mockWriter{}
225+
expectedStdout := "stdout line 1\nstdout line 2\nstdout line 3\n"
226+
expectedStderr := ""
227+
228+
agent := NewExternalAgentProcess("../testdata/agents/bash_stdout.sh", []string{}, stdout, stderr)
229+
230+
// When
231+
assert.NoError(agent.Start())
232+
assert.NoError(agent.Wait())
233+
234+
// Then
235+
assert.Equal(expectedStdout, string(bytes.Join(stdout.bytesReceived, []byte(""))))
236+
assert.Equal(expectedStderr, string(bytes.Join(stderr.bytesReceived, []byte(""))))
237+
}
238+
239+
func TestAgentStderrWriter(t *testing.T) {
240+
// Given
221241
assert := assert.New(t)
222-
buffer := &mockWriter{}
223242

224-
agent := NewExternalAgentProcess("../testdata/agents/bash_echo.sh", []string{}, buffer)
243+
stdout := &mockWriter{}
244+
stderr := &mockWriter{}
245+
expectedStdout := ""
246+
expectedStderr := "stderr line 1\nstderr line 2\nstderr line 3\n"
247+
248+
agent := NewExternalAgentProcess("../testdata/agents/bash_stderr.sh", []string{}, stdout, stderr)
249+
250+
// When
251+
assert.NoError(agent.Start())
252+
assert.NoError(agent.Wait())
253+
254+
// Then
255+
assert.Equal(expectedStdout, string(bytes.Join(stdout.bytesReceived, []byte(""))))
256+
assert.Equal(expectedStderr, string(bytes.Join(stderr.bytesReceived, []byte(""))))
257+
}
258+
259+
func TestAgentStdoutAndStderrSeperateWriters(t *testing.T) {
260+
// Given
261+
assert := assert.New(t)
262+
263+
stdout := &mockWriter{}
264+
stderr := &mockWriter{}
265+
expectedStdout := "stdout line 1\nstdout line 2\nstdout line 3\n"
266+
expectedStderr := "stderr line 1\nstderr line 2\nstderr line 3\n"
267+
268+
agent := NewExternalAgentProcess("../testdata/agents/bash_stdout_and_stderr.sh", []string{}, stdout, stderr)
225269

270+
// When
226271
assert.NoError(agent.Start())
227272
assert.NoError(agent.Wait())
228273

229-
message := "hello world\n|barbaz\n|hello world\n|barbaz2"
230-
assert.Equal(message, string(bytes.Join(buffer.bytesReceived, []byte("|"))))
274+
// Then
275+
assert.Equal(expectedStdout, string(bytes.Join(stdout.bytesReceived, []byte(""))))
276+
assert.Equal(expectedStderr, string(bytes.Join(stderr.bytesReceived, []byte(""))))
231277
}
232278

233279
type mockWriter struct {

lambda/appctx/appctxutil.go

+72-16
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ package appctx
55

66
import (
77
"context"
8-
"net/http"
9-
"strings"
10-
118
"go.amzn.com/lambda/fatalerror"
129
"go.amzn.com/lambda/interop"
10+
"net/http"
11+
"strings"
1312

1413
log "github.com/sirupsen/logrus"
1514
)
@@ -24,6 +23,9 @@ type ReqCtxKey int
2423
// context object into request context.
2524
const ReqCtxApplicationContextKey ReqCtxKey = iota
2625

26+
// MaxRuntimeReleaseLength Max length for user agent string.
27+
const MaxRuntimeReleaseLength = 128
28+
2729
// FromRequest retrieves application context from the request context.
2830
func FromRequest(request *http.Request) ApplicationContext {
2931
return request.Context().Value(ReqCtxApplicationContextKey).(ApplicationContext)
@@ -39,24 +41,78 @@ func GetRuntimeRelease(appCtx ApplicationContext) string {
3941
return appCtx.GetOrDefault(AppCtxRuntimeReleaseKey, "").(string)
4042
}
4143

42-
// UpdateAppCtxWithRuntimeRelease extracts runtime release info from user agent header and put it into appCtx.
44+
// GetUserAgentFromRequest Returns the first token -seperated by a space-
45+
// from request header 'User-Agent'.
46+
func GetUserAgentFromRequest(request *http.Request) string {
47+
runtimeRelease := ""
48+
userAgent := request.Header.Get("User-Agent")
49+
// Split around spaces and use only the first token.
50+
if fields := strings.Fields(userAgent); len(fields) > 0 && len(fields[0]) > 0 {
51+
runtimeRelease = fields[0]
52+
}
53+
return runtimeRelease
54+
}
55+
56+
// CreateRuntimeReleaseFromRequest Gets runtime features from request header
57+
// 'Lambda-Runtime-Features', and append it to the given runtime release.
58+
func CreateRuntimeReleaseFromRequest(request *http.Request, runtimeRelease string) string {
59+
lambdaRuntimeFeaturesHeader := request.Header.Get("Lambda-Runtime-Features")
60+
61+
// "(", ")" are not valid token characters, and potentially could invalidate runtime_release
62+
lambdaRuntimeFeaturesHeader = strings.ReplaceAll(lambdaRuntimeFeaturesHeader, "(", "")
63+
lambdaRuntimeFeaturesHeader = strings.ReplaceAll(lambdaRuntimeFeaturesHeader, ")", "")
64+
65+
numberOfAppendedFeatures := 0
66+
// Available length is a maximum length available for runtime features (including delimiters). From maximal runtime
67+
// release length we subtract what we already have plus 3 additional bytes for a space and a pair of brackets for
68+
// list of runtime features that is added later.
69+
runtimeReleaseLength := len(runtimeRelease)
70+
if runtimeReleaseLength == 0 {
71+
runtimeReleaseLength = len("Unknown")
72+
}
73+
availableLength := MaxRuntimeReleaseLength - runtimeReleaseLength - 3
74+
var lambdaRuntimeFeatures []string
75+
76+
for _, feature := range strings.Fields(lambdaRuntimeFeaturesHeader) {
77+
featureLength := len(feature)
78+
// If featureLength <= availableLength - numberOfAppendedFeatures
79+
// (where numberOfAppendedFeatures is equal to number of delimiters needed).
80+
if featureLength <= availableLength-numberOfAppendedFeatures {
81+
availableLength -= featureLength
82+
lambdaRuntimeFeatures = append(lambdaRuntimeFeatures, feature)
83+
numberOfAppendedFeatures++
84+
}
85+
}
86+
// Append valid features to runtime release.
87+
if len(lambdaRuntimeFeatures) > 0 {
88+
if runtimeRelease == "" {
89+
runtimeRelease = "Unknown"
90+
}
91+
runtimeRelease += " (" + strings.Join(lambdaRuntimeFeatures, " ") + ")"
92+
}
93+
94+
return runtimeRelease
95+
}
96+
97+
// UpdateAppCtxWithRuntimeRelease extracts runtime release info from user agent & lambda runtime features
98+
// headers and update it into appCtx.
4399
// Sample UA:
44100
// Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0
45101
func UpdateAppCtxWithRuntimeRelease(request *http.Request, appCtx ApplicationContext) bool {
46-
// If appCtx has runtime release value already, skip updating for consistency.
47-
if len(GetRuntimeRelease(appCtx)) > 0 {
48-
return false
49-
}
50-
51-
userAgent := request.Header.Get("User-Agent")
52-
if len(userAgent) == 0 {
102+
// If appCtx has runtime release value already, just append the runtime features.
103+
if appCtxRuntimeRelease := GetRuntimeRelease(appCtx); len(appCtxRuntimeRelease) > 0 {
104+
// if the runtime features are not appended before append them, otherwise ignore
105+
if runtimeReleaseWithFeatures := CreateRuntimeReleaseFromRequest(request, appCtxRuntimeRelease); len(runtimeReleaseWithFeatures) > len(appCtxRuntimeRelease) &&
106+
appCtxRuntimeRelease[len(appCtxRuntimeRelease)-1] != ')' {
107+
appCtx.Store(AppCtxRuntimeReleaseKey, runtimeReleaseWithFeatures)
108+
return true
109+
}
53110
return false
54111
}
55-
56-
// Split around spaces and use only the first token.
57-
if fields := strings.Fields(userAgent); len(fields) > 0 && len(fields[0]) > 0 {
58-
appCtx.Store(AppCtxRuntimeReleaseKey,
59-
fields[0])
112+
// If appCtx doesn't have runtime release value, update it with user agent and runtime features.
113+
if runtimeReleaseWithFeatures := CreateRuntimeReleaseFromRequest(request,
114+
GetUserAgentFromRequest(request)); runtimeReleaseWithFeatures != "" {
115+
appCtx.Store(AppCtxRuntimeReleaseKey, runtimeReleaseWithFeatures)
60116
return true
61117
}
62118
return false

lambda/appctx/appctxutil_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package appctx
55

66
import (
77
"net/http/httptest"
8+
"strings"
89
"testing"
910

1011
"github.com/stretchr/testify/assert"
@@ -30,6 +31,63 @@ func runTestRequestWithUserAgent(t *testing.T, userAgent string, expectedRuntime
3031
assert.Equal(t, expectedRuntimeRelease, ctxRuntimeRelease, "failed to extract runtime_release token")
3132
}
3233

34+
func TestCreateRuntimeReleaseFromRequest(t *testing.T) {
35+
tests := map[string]struct {
36+
userAgentHeader string
37+
lambdaRuntimeFeaturesHeader string
38+
expectedRuntimeRelease string
39+
}{
40+
"No User-Agent header": {
41+
userAgentHeader: "",
42+
lambdaRuntimeFeaturesHeader: "httpcl/2.0 execwr",
43+
expectedRuntimeRelease: "Unknown (httpcl/2.0 execwr)",
44+
},
45+
"No Lambda-Runtime-Features header": {
46+
userAgentHeader: "Node.js/14.16.0",
47+
lambdaRuntimeFeaturesHeader: "",
48+
expectedRuntimeRelease: "Node.js/14.16.0",
49+
},
50+
"Lambda-Runtime-Features header with additional spaces": {
51+
userAgentHeader: "Node.js/14.16.0",
52+
lambdaRuntimeFeaturesHeader: "httpcl/2.0 execwr",
53+
expectedRuntimeRelease: "Node.js/14.16.0 (httpcl/2.0 execwr)",
54+
},
55+
"Lambda-Runtime-Features header with special characters": {
56+
userAgentHeader: "Node.js/14.16.0",
57+
lambdaRuntimeFeaturesHeader: "httpcl/2.0@execwr-1 abcd?efg nodewr/(4.33)) nodewr/4.3",
58+
expectedRuntimeRelease: "Node.js/14.16.0 (httpcl/2.0@execwr-1 abcd?efg nodewr/4.33 nodewr/4.3)",
59+
},
60+
"Lambda-Runtime-Features header with long Lambda-Runtime-Features header": {
61+
userAgentHeader: "Node.js/14.16.0",
62+
lambdaRuntimeFeaturesHeader: strings.Repeat("abcdef ", MaxRuntimeReleaseLength/7),
63+
expectedRuntimeRelease: "Node.js/14.16.0 (" + strings.Repeat("abcdef ", (MaxRuntimeReleaseLength-18-6)/7) + "abcdef)",
64+
},
65+
"Lambda-Runtime-Features header with long Lambda-Runtime-Features header with UTF-8 characters": {
66+
userAgentHeader: "Node.js/14.16.0",
67+
lambdaRuntimeFeaturesHeader: strings.Repeat("我爱亚马逊 ", MaxRuntimeReleaseLength/16),
68+
expectedRuntimeRelease: "Node.js/14.16.0 (" + strings.Repeat("我爱亚马逊 ", (MaxRuntimeReleaseLength-18-15)/16) + "我爱亚马逊)",
69+
},
70+
}
71+
72+
for _, tc := range tests {
73+
req := httptest.NewRequest("", "/", nil)
74+
if tc.userAgentHeader != "" {
75+
req.Header.Set("User-Agent", tc.userAgentHeader)
76+
}
77+
if tc.lambdaRuntimeFeaturesHeader != "" {
78+
req.Header.Set("Lambda-Runtime-Features", tc.lambdaRuntimeFeaturesHeader)
79+
}
80+
appCtx := NewApplicationContext()
81+
request := RequestWithAppCtx(req, appCtx)
82+
83+
UpdateAppCtxWithRuntimeRelease(request, appCtx)
84+
runtimeRelease := GetRuntimeRelease(appCtx)
85+
86+
assert.LessOrEqual(t, len(runtimeRelease), MaxRuntimeReleaseLength)
87+
assert.Equal(t, tc.expectedRuntimeRelease, runtimeRelease)
88+
}
89+
}
90+
3391
func TestUpdateAppCtxWithRuntimeRelease(t *testing.T) {
3492
type pair struct {
3593
in, wanted string
@@ -74,6 +132,25 @@ func TestUpdateAppCtxWithRuntimeReleaseWithBlankUserAgent(t *testing.T) {
74132
assert.False(t, ok)
75133
}
76134

135+
func TestUpdateAppCtxWithRuntimeReleaseWithLambdaRuntimeFeatures(t *testing.T) {
136+
// GIVEN
137+
// Simple LambdaRuntimeFeatures passed.
138+
req := httptest.NewRequest("", "/", nil)
139+
req.Header.Set("User-Agent", "Node.js/14.16.0")
140+
req.Header.Set("Lambda-Runtime-Features", "httpcl/2.0 execwr nodewr/4.3")
141+
request := RequestWithAppCtx(req, NewApplicationContext())
142+
appCtx := request.Context().Value(ReqCtxApplicationContextKey).(ApplicationContext)
143+
144+
// DO
145+
ok := UpdateAppCtxWithRuntimeRelease(request, appCtx)
146+
147+
//ASSERT
148+
assert.True(t, ok, "runtime_release updated based only on User-Agent and valid features")
149+
ctxRuntimeRelease, ok := appCtx.Load(AppCtxRuntimeReleaseKey)
150+
assert.True(t, ok)
151+
assert.Equal(t, "Node.js/14.16.0 (httpcl/2.0 execwr nodewr/4.3)", ctxRuntimeRelease)
152+
}
153+
77154
// Test that RAPID allows updating runtime_release only once
78155
func TestUpdateAppCtxWithRuntimeReleaseMultipleTimes(t *testing.T) {
79156
// GIVEN

0 commit comments

Comments
 (0)