Skip to content

Commit 275dffc

Browse files
authored
aws/ec2metadata`: modifies IMDS client to use shorter request timeout
The PR modifies EC2Metadata client to use request context within its operations. Reduces the dialer timeout and response header timeout on the EC2Metadata client to help reduce latency for known issues with EC2Metadata client running inside a container.
1 parent f6d7c84 commit 275dffc

File tree

5 files changed

+55
-4
lines changed

5 files changed

+55
-4
lines changed

CHANGELOG_PENDING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ SDK Enhancements
2121
* Adds an implementation of RingBuffer data structure which acts as a revolving buffer of a predefined length. The RingBuffer implements io.ReadWriter interface.
2222
* Adds unit tests to test the behavior of the ring buffer.
2323
* `aws/ec2metadata`: Adds support for EC2Metadata client to use secure tokens provided by the IMDS ([#453](https://github.com/aws/aws-sdk-go-v2/pull/453))
24+
* Modifies EC2Metadata client to use request context within its operations ([#462](https://github.com/aws/aws-sdk-go-v2/pull/462))
25+
* Reduces the default dialer timeout and response header timeout to help reduce latency for known issues with EC2Metadata client running inside a container
2426
* Modifies and adds tests to verify the behavior of the EC2Metadata client.
2527
* `service/dynamodb/dynamodbattribute`: Adds clarifying docs on dynamodbattribute.UnixTime ([#464](https://github.com/aws/aws-sdk-go-v2/pull/464))
2628
* `example/service/sts/assumeRole`: added sts assume role example ([#224](https://github.com/aws/aws-sdk-go-v2/pull/224))

aws/ec2metadata/api_client.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package ec2metadata
99

1010
import (
1111
"bytes"
12+
"context"
1213
"errors"
1314
"io"
1415
"net"
@@ -33,11 +34,18 @@ const (
3334
tokenHeader = "x-aws-ec2-metadata-token"
3435

3536
// Named Handler constants
37+
contextWithTimeoutHandlerName = "ContextWithTimeoutHandler"
38+
cancelContextHandlerName = "CancelContextHandler"
3639
fetchTokenHandlerName = "FetchTokenHandler"
3740
unmarshalMetadataHandlerName = "unmarshalMetadataHandler"
3841
unmarshalTokenHandlerName = "unmarshalTokenHandler"
3942
enableTokenProviderHandlerName = "enableTokenProviderHandler"
4043

44+
// client constants
45+
defaultClientContextTimeout = 5 * time.Second
46+
defaultDialerTimeout = 250 * time.Millisecond
47+
defaultResponseHeaderTimeout = 500 * time.Millisecond
48+
4149
// TTL constants
4250
defaultTTL = 21600 * time.Second
4351
ttlExpirationWindow = 30 * time.Second
@@ -64,7 +72,15 @@ func New(config aws.Config) *Client {
6472
// environment with the service present. The client should fail fast in
6573
// this case.
6674
config.HTTPClient = c.WithDialerOptions(func(d *net.Dialer) {
67-
d.Timeout = 5 * time.Second
75+
d.Timeout = defaultDialerTimeout
76+
})
77+
78+
// Use a custom Transport timeout for the EC2 Metadata service to account
79+
// for the possibility that the application might be running in a container,
80+
// and EC2Metadata service drops the connection after a single IP Hop. The client
81+
// should fail fast in this case.
82+
config.HTTPClient = c.WithTransportOptions(func(tr *http.Transport) {
83+
tr.ResponseHeaderTimeout = defaultResponseHeaderTimeout
6884
})
6985
}
7086

@@ -87,6 +103,23 @@ func New(config aws.Config) *Client {
87103
Name: fetchTokenHandlerName,
88104
Fn: tp.fetchTokenHandler,
89105
})
106+
107+
// The context With timeout handler function wraps a context with timeout and sets it on a request.
108+
// It also sets a handler on complete handler stack that cancels the context
109+
svc.Handlers.Send.PushFrontNamed(aws.NamedHandler{
110+
Name: contextWithTimeoutHandlerName,
111+
Fn: func(r *aws.Request) {
112+
ctx, cancelFn := context.WithTimeout(r.Context(), defaultClientContextTimeout)
113+
r.SetContext(ctx)
114+
r.Handlers.Complete.PushBackNamed(aws.NamedHandler{
115+
Name: cancelContextHandlerName,
116+
Fn: func(r *aws.Request) {
117+
cancelFn()
118+
},
119+
})
120+
},
121+
})
122+
90123
// NamedHandler for enabling token provider
91124
svc.Handlers.Complete.PushBackNamed(aws.NamedHandler{
92125
Name: enableTokenProviderHandlerName,

aws/ec2metadata/api_ops_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,10 @@ func TestEC2MetadataRetryFailure(t *testing.T) {
761761
cfg := unit.Config()
762762
cfg.EndpointResolver = aws.EndpointResolverFunc(myCustomResolver)
763763
c := ec2metadata.New(cfg)
764+
// mock retryer with minimum throttle delay set to 1 ms
765+
c.Retryer = aws.NewDefaultRetryer(func(d *aws.DefaultRetryer) {
766+
d.MinThrottleDelay = 1 * time.Millisecond
767+
})
764768
// Handler on client that logs if retried
765769
c.Handlers.AfterRetry.PushBack(func(i *aws.Request) {
766770
t.Logf("%v received, retrying operation %v", i.HTTPResponse.StatusCode, i.Operation.Name)
@@ -794,7 +798,7 @@ func TestEC2MetadataRetryOnce(t *testing.T) {
794798
mux.HandleFunc("/latest/api/token", func(w http.ResponseWriter, r *http.Request) {
795799
if r.Method == "PUT" && r.Header.Get(ttlHeader) != "" {
796800
w.Header().Set(ttlHeader, "200")
797-
for retry {
801+
if retry {
798802
retry = false
799803
http.Error(w, "service unavailable", http.StatusServiceUnavailable)
800804
return
@@ -824,6 +828,10 @@ func TestEC2MetadataRetryOnce(t *testing.T) {
824828
cfg := unit.Config()
825829
cfg.EndpointResolver = aws.EndpointResolverFunc(myCustomResolver)
826830
c := ec2metadata.New(cfg)
831+
// mock retryer with minimum throttle delay set to 1 ms
832+
c.Retryer = aws.NewDefaultRetryer(func(d *aws.DefaultRetryer) {
833+
d.MinThrottleDelay = 1 * time.Millisecond
834+
})
827835
// Handler on client that logs if retried
828836
c.Handlers.AfterRetry.PushBack(func(i *aws.Request) {
829837
t.Logf("%v received, retrying operation %v", i.HTTPResponse.StatusCode, i.Operation.Name)
@@ -1088,9 +1096,13 @@ func TestRequestTimeOut(t *testing.T) {
10881096
}
10891097

10901098
c.Handlers.Complete.PushBack(op.addToOperationPerformedList)
1091-
1099+
start := time.Now()
10921100
resp, err := c.GetMetadata(context.Background(), "/some/path")
10931101

1102+
if e, a := 1*time.Second, time.Since(start); e < a {
1103+
t.Fatalf("expected duration of test to be less than %v, got %v", e, a)
1104+
}
1105+
10941106
expectedOperationsPerformed := []string{"GetToken", "GetMetadata"}
10951107

10961108
if e, a := "IMDSProfileForSDKGo", resp; e != a {
@@ -1105,7 +1117,11 @@ func TestRequestTimeOut(t *testing.T) {
11051117
t.Fatalf("Found diff in operations performed: \n %v \n", diff)
11061118
}
11071119

1120+
start = time.Now()
11081121
resp, err = c.GetMetadata(context.Background(), "/some/path")
1122+
if e, a := 1*time.Second, time.Since(start); e < a {
1123+
t.Fatalf("expected duration of test to be less than %v, got %v", e, a)
1124+
}
11091125

11101126
expectedOperationsPerformed = []string{"GetToken", "GetMetadata", "GetMetadata"}
11111127

aws/signer/v2/v2_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ func TestSignRequestWithAndWithoutSession(t *testing.T) {
6363
}
6464

6565
// create request without a SecurityToken (session) in the credentials
66-
6766
query := newQuery()
6867
timestamp := time.Date(2015, 7, 16, 7, 56, 16, 0, time.UTC)
6968
builder := signerBuilder{

aws/signer/v4/v4.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ func buildQuery(r rule, header http.Header) (url.Values, http.Header) {
563563

564564
return query, unsignedHeaders
565565
}
566+
566567
func (ctx *signingCtx) buildCanonicalHeaders(r rule, header http.Header) {
567568
var headers []string
568569
headers = append(headers, "host")

0 commit comments

Comments
 (0)