Skip to content

Commit 75c63b9

Browse files
authored
fix(compute/metadata): fix retry logic to not panic on error (#4714)
In the case of an error res will be nil. Also, we should not break on nil err as it might be eligible for a retry from the status code. Fixes: #4713
1 parent a0f7a02 commit 75c63b9

File tree

4 files changed

+117
-6
lines changed

4 files changed

+117
-6
lines changed

compute/metadata/metadata.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -308,20 +308,24 @@ func (c *Client) getETag(suffix string) (value, etag string, err error) {
308308
req.Header.Set("Metadata-Flavor", "Google")
309309
req.Header.Set("User-Agent", userAgent)
310310
var res *http.Response
311+
var reqErr error
311312
retryer := newRetryer()
312313
for {
313-
var err error
314-
res, err = c.hc.Do(req)
315-
if err == nil {
316-
break
314+
res, reqErr = c.hc.Do(req)
315+
var code int
316+
if res != nil {
317+
code = res.StatusCode
317318
}
318-
if delay, shouldRetry := retryer.Retry(res.StatusCode, err); shouldRetry {
319+
if delay, shouldRetry := retryer.Retry(code, reqErr); shouldRetry {
319320
if err := gax.Sleep(ctx, delay); err != nil {
320321
return "", "", err
321322
}
322323
continue
323324
}
324-
return "", "", err
325+
break
326+
}
327+
if reqErr != nil {
328+
return "", "", nil
325329
}
326330
defer res.Body.Close()
327331
if res.StatusCode == http.StatusNotFound {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2016 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//go:build go1.13
16+
// +build go1.13
17+
18+
package metadata
19+
20+
import (
21+
"io"
22+
"io/ioutil"
23+
"net/http"
24+
"strings"
25+
"testing"
26+
)
27+
28+
func TestRetry(t *testing.T) {
29+
tests := []struct {
30+
name string
31+
timesToFail int
32+
failCode int
33+
failErr error
34+
response string
35+
}{
36+
{
37+
name: "no retries",
38+
response: "test",
39+
},
40+
{
41+
name: "retry 500 once",
42+
response: "test",
43+
failCode: 500,
44+
timesToFail: 1,
45+
},
46+
{
47+
name: "retry io.ErrUnexpectedEOF once",
48+
response: "test",
49+
failErr: io.ErrUnexpectedEOF,
50+
timesToFail: 1,
51+
},
52+
}
53+
for _, tt := range tests {
54+
t.Run(tt.name, func(t *testing.T) {
55+
ft := &failingTransport{
56+
timesToFail: tt.timesToFail,
57+
failCode: tt.failCode,
58+
failErr: tt.failErr,
59+
response: tt.response,
60+
}
61+
c := NewClient(&http.Client{Transport: ft})
62+
s, err := c.Get("")
63+
if err != nil {
64+
t.Fatalf("unexpected error: %v", err)
65+
}
66+
if ft.called != ft.failedAttempts+1 {
67+
t.Fatalf("failed %d times, want %d", ft.called, ft.failedAttempts+1)
68+
}
69+
if s != tt.response {
70+
t.Fatalf("c.Get() = %q, want %q", s, tt.response)
71+
}
72+
})
73+
}
74+
}
75+
76+
type failingTransport struct {
77+
timesToFail int
78+
failCode int
79+
failErr error
80+
response string
81+
82+
failedAttempts int
83+
called int
84+
}
85+
86+
func (r *failingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
87+
r.called++
88+
if r.failedAttempts < r.timesToFail {
89+
r.failedAttempts++
90+
if r.failErr != nil {
91+
return nil, r.failErr
92+
}
93+
return &http.Response{StatusCode: r.failCode}, nil
94+
}
95+
return &http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(strings.NewReader(r.response))}, nil
96+
}

compute/metadata/retry.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package metadata
1616

1717
import (
1818
"io"
19+
"net/http"
1920
"time"
2021

2122
"github.com/googleapis/gax-go/v2"
@@ -43,6 +44,9 @@ type metadataRetryer struct {
4344
}
4445

4546
func (r *metadataRetryer) Retry(status int, err error) (time.Duration, bool) {
47+
if status == http.StatusOK {
48+
return 0, false
49+
}
4650
retryOk := shouldRetry(status, err)
4751
if !retryOk {
4852
return 0, false

compute/metadata/retry_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ func TestMetadataRetryer(t *testing.T) {
8787
wantDelay: 0,
8888
wantShouldRetry: false,
8989
},
90+
{
91+
name: "don't retry 200",
92+
code: 200,
93+
err: nil,
94+
wantDelay: 0,
95+
wantShouldRetry: false,
96+
},
9097
}
9198

9299
for _, tc := range tests {

0 commit comments

Comments
 (0)