Skip to content

Commit 0243175

Browse files
authored
Prep azcore v1.6.1 for release (#20961)
* Retry policy will always clone the *http.Request (#20843) * Retry policy will always clone the *http.Request This ensures that the entirety of the request is restored on retries. * simplify test * Handle more error codes when an RP isn't registered (#20848) There's more than one error code returned from unregistered RPs. * Add another non-standard error code for RP registration (#20860) * Prep azcore v1.6.1 for release Cherry-picked the following commits. - a3b2c13 - 889be58 - 6879d7e
1 parent 9c9d62a commit 0243175

File tree

6 files changed

+81
-19
lines changed

6 files changed

+81
-19
lines changed

sdk/azcore/CHANGELOG.md

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
# Release History
22

3-
## 1.6.1 (Unreleased)
4-
5-
### Features Added
6-
7-
### Breaking Changes
3+
## 1.6.1 (2023-06-06)
84

95
### Bugs Fixed
10-
11-
### Other Changes
6+
* Retry policy always clones the underlying `*http.Request` before invoking the next policy.
7+
* Added some non-standard error codes to the list of error codes for unregistered resource providers.
128

139
## 1.6.0 (2023-05-04)
1410

sdk/azcore/arm/runtime/pipeline_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestDisableAutoRPRegistration(t *testing.T) {
104104
srv, close := mock.NewServer()
105105
defer close()
106106
// initial response that RP is unregistered
107-
srv.SetResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp)))
107+
srv.SetResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp1)))
108108
opts := &armpolicy.ClientOptions{DisableRPRegistration: true, ClientOptions: policy.ClientOptions{Transport: srv}}
109109
req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL())
110110
if err != nil {

sdk/azcore/arm/runtime/policy_register_rp.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
8080
// policy is disabled
8181
return req.Next()
8282
}
83-
const unregisteredRPCode = "MissingSubscriptionRegistration"
8483
const registeredState = "Registered"
8584
var rp string
8685
var resp *http.Response
@@ -101,7 +100,7 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
101100
// to the caller so its error unmarshalling will kick in
102101
return resp, err
103102
}
104-
if !strings.EqualFold(reqErr.ServiceError.Code, unregisteredRPCode) {
103+
if !isUnregisteredRPCode(reqErr.ServiceError.Code) {
105104
// not a 409 due to unregistered RP. just return the response
106105
// to the caller so its error unmarshalling will kick in
107106
return resp, err
@@ -173,6 +172,21 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
173172
return resp, fmt.Errorf("exceeded attempts to register %s", rp)
174173
}
175174

175+
var unregisteredRPCodes = []string{
176+
"MissingSubscriptionRegistration",
177+
"MissingRegistrationForResourceProvider",
178+
"Subscription Not Registered",
179+
}
180+
181+
func isUnregisteredRPCode(errorCode string) bool {
182+
for _, code := range unregisteredRPCodes {
183+
if strings.EqualFold(errorCode, code) {
184+
return true
185+
}
186+
}
187+
return false
188+
}
189+
176190
func getSubscription(path string) (string, error) {
177191
parts := strings.Split(path, "/")
178192
for i, v := range parts {

sdk/azcore/arm/runtime/policy_register_rp_test.go

+26-8
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
"github.com/stretchr/testify/require"
2525
)
2626

27-
const rpUnregisteredResp = `{
27+
const rpUnregisteredResp1 = `{
2828
"error":{
2929
"code":"MissingSubscriptionRegistration",
3030
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.Storage'. See https://aka.ms/rps-not-found for how to register subscriptions.",
@@ -37,6 +37,19 @@ const rpUnregisteredResp = `{
3737
}
3838
}`
3939

40+
const rpUnregisteredResp2 = `{
41+
"error":{
42+
"code":"MissingRegistrationForResourceProvider",
43+
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.Storage'. See https://aka.ms/rps-not-found for how to register subscriptions.",
44+
"details":[{
45+
"code":"MissingRegistrationForResourceProvider",
46+
"target":"Microsoft.Storage",
47+
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.Storage'. See https://aka.ms/rps-not-found for how to register subscriptions."
48+
}
49+
]
50+
}
51+
}`
52+
4053
// some content was omitted here as it's not relevant
4154
const rpRegisteringResp = `{
4255
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Storage",
@@ -89,7 +102,7 @@ func TestRPRegistrationPolicySuccess(t *testing.T) {
89102
srv, close := mock.NewServer()
90103
defer close()
91104
// initial response that RP is unregistered
92-
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp)))
105+
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp1)))
93106
// polling responses to Register() and Get(), in progress
94107
srv.RepeatResponse(5, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp)))
95108
// polling response, successful registration
@@ -180,7 +193,7 @@ func TestRPRegistrationPolicyTimesOut(t *testing.T) {
180193
srv, close := mock.NewServer()
181194
defer close()
182195
// initial response that RP is unregistered
183-
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp)))
196+
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp1)))
184197
// polling responses to Register() and Get(), in progress but slow
185198
// tests registration takes too long, times out
186199
srv.RepeatResponse(10, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp)), mock.WithSlowResponse(400*time.Millisecond))
@@ -212,7 +225,7 @@ func TestRPRegistrationPolicyExceedsAttempts(t *testing.T) {
212225
// add a cycle of unregistered->registered so that we keep retrying and hit the cap
213226
for i := 0; i < 4; i++ {
214227
// initial response that RP is unregistered
215-
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp)))
228+
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp1)))
216229
// polling responses to Register() and Get(), in progress
217230
srv.RepeatResponse(2, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp)))
218231
// polling response, successful registration
@@ -246,7 +259,7 @@ func TestRPRegistrationPolicyCanCancel(t *testing.T) {
246259
srv, close := mock.NewServer()
247260
defer close()
248261
// initial response that RP is unregistered
249-
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp)))
262+
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp2)))
250263
// polling responses to Register() and Get(), in progress but slow so we have time to cancel
251264
srv.RepeatResponse(10, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp)), mock.WithSlowResponse(300*time.Millisecond))
252265
// log only RP registration
@@ -287,7 +300,7 @@ func TestRPRegistrationPolicyDisabled(t *testing.T) {
287300
srv, close := mock.NewServer()
288301
defer close()
289302
// initial response that RP is unregistered
290-
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp)))
303+
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp2)))
291304
ops := testRPRegistrationOptions(srv)
292305
ops.MaxAttempts = -1
293306
client := newFakeClient(t, srv, ops)
@@ -305,7 +318,7 @@ func TestRPRegistrationPolicyDisabled(t *testing.T) {
305318
require.Error(t, err)
306319
var respErr *exported.ResponseError
307320
require.ErrorAs(t, err, &respErr)
308-
require.EqualValues(t, "MissingSubscriptionRegistration", respErr.ErrorCode)
321+
require.EqualValues(t, "MissingRegistrationForResourceProvider", respErr.ErrorCode)
309322
require.Zero(t, resp)
310323
// shouldn't be any log entries
311324
require.Zero(t, logEntries)
@@ -315,7 +328,7 @@ func TestRPRegistrationPolicyAudience(t *testing.T) {
315328
srv, close := mock.NewServer()
316329
defer close()
317330
// initial response that RP is unregistered
318-
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp)))
331+
srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp2)))
319332
// polling responses to Register() and Get(), in progress
320333
srv.AppendResponse(mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp)))
321334
// polling response, successful registration
@@ -399,6 +412,11 @@ func TestRPRegistrationPolicyEnvironmentsInSubExceeded(t *testing.T) {
399412
require.EqualValues(t, 0, logEntries)
400413
}
401414

415+
func TestIsUnregisteredRPCode(t *testing.T) {
416+
require.True(t, isUnregisteredRPCode("Subscription Not Registered"))
417+
require.False(t, isUnregisteredRPCode("Your subscription isn't registered"))
418+
}
419+
402420
type fakeClient struct {
403421
ep string
404422
pl runtime.Pipeline

sdk/azcore/runtime/policy_retry.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ func (p *retryPolicy) Do(req *policy.Request) (resp *http.Response, err error) {
125125
}
126126

127127
if options.TryTimeout == 0 {
128-
resp, err = req.Next()
128+
clone := req.Clone(req.Raw().Context())
129+
resp, err = clone.Next()
129130
} else {
130131
// Set the per-try time for this particular retry operation and then Do the operation.
131132
tryCtx, tryCancel := context.WithTimeout(req.Raw().Context(), options.TryTimeout)

sdk/azcore/runtime/policy_retry_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,39 @@ func TestRetryableRequestBodyWithCloser(t *testing.T) {
810810
require.True(t, tr.closeCalled)
811811
}
812812

813+
func TestRetryPolicySuccessWithRetryPreserveHeaders(t *testing.T) {
814+
srv, close := mock.NewServer()
815+
defer close()
816+
srv.AppendResponse(mock.WithStatusCode(http.StatusRequestTimeout))
817+
srv.AppendResponse()
818+
pl := exported.NewPipeline(srv, NewRetryPolicy(testRetryOptions()), exported.PolicyFunc(challengeLikePolicy))
819+
req, err := NewRequest(context.Background(), http.MethodGet, srv.URL())
820+
require.NoError(t, err)
821+
body := newRewindTrackingBody("stuff")
822+
require.NoError(t, req.SetBody(body, "text/plain"))
823+
resp, err := pl.Do(req)
824+
require.NoError(t, err)
825+
require.EqualValues(t, http.StatusOK, resp.StatusCode)
826+
require.EqualValues(t, 2, srv.Requests())
827+
require.EqualValues(t, 1, body.rcount)
828+
require.True(t, body.closed)
829+
}
830+
831+
func challengeLikePolicy(req *policy.Request) (*http.Response, error) {
832+
if req.Body() == nil {
833+
return nil, errors.New("request body wasn't restored")
834+
}
835+
if req.Raw().Header.Get("content-type") != "text/plain" {
836+
return nil, errors.New("content-type header wasn't restored")
837+
}
838+
839+
// remove the body and header. the retry policy should restore them
840+
if err := req.SetBody(nil, ""); err != nil {
841+
return nil, err
842+
}
843+
return req.Next()
844+
}
845+
813846
func newRewindTrackingBody(s string) *rewindTrackingBody {
814847
// there are two rewinds that happen before rewinding for a retry
815848
// 1. to get the body's size in SetBody()

0 commit comments

Comments
 (0)