Skip to content

Commit d44a9a5

Browse files
do not retry if getBody not given (#3299) (#5977)
Signed-off-by: Modular Magician <[email protected]>
1 parent ce3e6f2 commit d44a9a5

File tree

3 files changed

+64
-16
lines changed

3 files changed

+64
-16
lines changed

.changelog/3299.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
provider: Fixed an error with resources failing to upload large files (e.g. with `google_storage_bucket_object`) during retried requests
3+
```

google/retry_transport.go

+16-16
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"context"
3535
"errors"
3636
"fmt"
37-
"github.com/hashicorp/errwrap"
3837
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
3938
"google.golang.org/api/googleapi"
4039
"io/ioutil"
@@ -99,19 +98,29 @@ func (t *retryTransport) RoundTrip(req *http.Request) (resp *http.Response, resp
9998
backoff := time.Millisecond * 500
10099
nextBackoff := time.Millisecond * 500
101100

101+
// VCR depends on the original request body being consumed, so
102+
// consume here. Since this won't affect the request itself,
103+
// we do this before the actual Retry loop so we can consume the request Body as needed
104+
// e.g. if the request couldn't be retried, we use the original request
105+
if _, err := httputil.DumpRequestOut(req, true); err != nil {
106+
log.Printf("[WARN] Retry Transport: Consuming original request body failed: %v", err)
107+
}
108+
102109
log.Printf("[DEBUG] Retry Transport: starting RoundTrip retry loop")
103110
Retry:
104111
for {
105-
log.Printf("[DEBUG] Retry Transport: request attempt %d", attempts)
106-
107-
// Copy the request - we dont want to use the original request as
108-
// RoundTrip contract says request body can/will be consumed
112+
// RoundTrip contract says request body can/will be consumed, so we need to
113+
// copy the request body for each attempt.
114+
// If we can't copy the request, we run as a single request.
109115
newRequest, copyErr := copyHttpRequest(req)
110116
if copyErr != nil {
111-
respErr = errwrap.Wrapf("unable to copy invalid http.Request for retry: {{err}}", copyErr)
117+
log.Printf("[WARN] Retry Transport: Unable to copy request body: %v.", copyErr)
118+
log.Printf("[WARN] Retry Transport: Running request as non-retryable")
119+
resp, respErr = t.internal.RoundTrip(req)
112120
break Retry
113121
}
114122

123+
log.Printf("[DEBUG] Retry Transport: request attempt %d", attempts)
115124
// Do the wrapped Roundtrip. This is one request in the retry loop.
116125
resp, respErr = t.internal.RoundTrip(newRequest)
117126
attempts++
@@ -141,13 +150,6 @@ Retry:
141150
continue
142151
}
143152
}
144-
145-
// VCR depends on the original request body being consumed, so consume it here
146-
_, err := httputil.DumpRequestOut(req, true)
147-
if err != nil {
148-
log.Printf("[DEBUG] Retry Transport: Reading request failed: %v", err)
149-
}
150-
151153
log.Printf("[DEBUG] Retry Transport: Returning after %d attempts", attempts)
152154
return resp, respErr
153155
}
@@ -157,15 +159,13 @@ Retry:
157159
// so it can be consumed.
158160
func copyHttpRequest(req *http.Request) (*http.Request, error) {
159161
newRequest := *req
160-
161162
if req.Body == nil || req.Body == http.NoBody {
162163
return &newRequest, nil
163164
}
164-
165165
// Helpers like http.NewRequest add a GetBody for copying.
166166
// If not given, we should reject the request.
167167
if req.GetBody == nil {
168-
return nil, errors.New("invalid HTTP request for transport, expected request.GetBody for non-empty Body")
168+
return nil, errors.New("request.GetBody is not defined for non-empty Body")
169169
}
170170

171171
bd, err := req.GetBody()

google/retry_transport_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,51 @@ func TestRetryTransport_SuccessWithBody(t *testing.T) {
123123
testRetryTransport_checkBody(t, resp, body)
124124
}
125125

126+
// Check for no and no retries if the request has no getBody (it should only run once)
127+
func TestRetryTransport_DoesNotRetryEmptyGetBody(t *testing.T) {
128+
msg := "non empty body"
129+
attempted := false
130+
131+
ts, client := setUpRetryTransportServerClient(http.HandlerFunc(
132+
func(w http.ResponseWriter, r *http.Request) {
133+
// Check for request body
134+
dump, err := ioutil.ReadAll(r.Body)
135+
if err != nil {
136+
w.WriteHeader(testRetryTransportCodeFailure)
137+
if _, werr := w.Write([]byte(fmt.Sprintf("got error: %v", err))); werr != nil {
138+
t.Errorf("[ERROR] unable to write to response writer: %v", err)
139+
}
140+
}
141+
dumpS := string(dump)
142+
if dumpS != msg {
143+
w.WriteHeader(testRetryTransportCodeFailure)
144+
if _, werr := w.Write([]byte(fmt.Sprintf("got unexpected body: %s", dumpS))); werr != nil {
145+
t.Errorf("[ERROR] unable to write to response writer: %v", err)
146+
}
147+
}
148+
if attempted {
149+
w.WriteHeader(testRetryTransportCodeFailure)
150+
if _, werr := w.Write([]byte("expected only one try")); werr != nil {
151+
t.Errorf("[ERROR] unable to write to response writer: %v", err)
152+
}
153+
}
154+
attempted = true
155+
w.WriteHeader(testRetryTransportCodeRetry)
156+
}))
157+
defer ts.Close()
158+
159+
// Create request
160+
req, err := http.NewRequest("GET", ts.URL, strings.NewReader(msg))
161+
if err != nil {
162+
t.Errorf("[ERROR] unable to make test request: %v", err)
163+
}
164+
// remove GetBody
165+
req.GetBody = nil
166+
167+
resp, err := client.Do(req)
168+
testRetryTransport_checkFailedWhileRetrying(t, resp, err)
169+
}
170+
126171
// handlers
127172
func testRetryTransportHandler_noRetries(t *testing.T, code int) http.Handler {
128173
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
 (0)