Skip to content

Commit bd05f59

Browse files
authored
fix: retry bulk update endpoint on error (#3492)
1 parent 27ca207 commit bd05f59

File tree

2 files changed

+70
-4
lines changed

2 files changed

+70
-4
lines changed

pkg/function/batch.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,15 @@ OUTER:
8484
policy.Reset()
8585
}
8686
if len(toUpdate) > 1 {
87-
if resp, err := s.client.V1BulkUpdateFunctionsWithResponse(ctx, s.project, toUpdate); err != nil {
88-
return errors.Errorf("failed to bulk update: %w", err)
89-
} else if resp.JSON200 == nil {
90-
return errors.Errorf("unexpected bulk update status %d: %s", resp.StatusCode(), string(resp.Body))
87+
if err := backoff.Retry(func() error {
88+
if resp, err := s.client.V1BulkUpdateFunctionsWithResponse(ctx, s.project, toUpdate); err != nil {
89+
return errors.Errorf("failed to bulk update: %w", err)
90+
} else if resp.JSON200 == nil {
91+
return errors.Errorf("unexpected bulk update status %d: %s", resp.StatusCode(), string(resp.Body))
92+
}
93+
return nil
94+
}, policy); err != nil {
95+
return err
9196
}
9297
}
9398
return nil

pkg/function/batch_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,65 @@ func TestUpsertFunctions(t *testing.T) {
4242
era.eszip = &MockBundler{}
4343
})
4444

45+
t.Run("deploys with bulk update", func(t *testing.T) {
46+
// Setup mock api
47+
defer gock.OffAll()
48+
gock.New(mockApiHost).
49+
Get("/v1/projects/" + mockProject + "/functions").
50+
Reply(http.StatusOK).
51+
JSON([]api.FunctionResponse{{Slug: "test-a"}})
52+
gock.New(mockApiHost).
53+
Patch("/v1/projects/" + mockProject + "/functions/test-a").
54+
Reply(http.StatusOK).
55+
JSON(api.FunctionResponse{Slug: "test-a"})
56+
gock.New(mockApiHost).
57+
Post("/v1/projects/" + mockProject + "/functions/test-b").
58+
Reply(http.StatusOK).
59+
JSON(api.FunctionResponse{Slug: "test-b"})
60+
gock.New(mockApiHost).
61+
Put("/v1/projects/" + mockProject + "/functions").
62+
ReplyError(errors.New("network error"))
63+
gock.New(mockApiHost).
64+
Put("/v1/projects/" + mockProject + "/functions").
65+
Reply(http.StatusServiceUnavailable)
66+
gock.New(mockApiHost).
67+
Put("/v1/projects/" + mockProject + "/functions").
68+
Reply(http.StatusOK).
69+
JSON(api.V1BulkUpdateFunctionsResponse{})
70+
// Run test
71+
err := client.UpsertFunctions(context.Background(), config.FunctionConfig{
72+
"test-a": {},
73+
"test-b": {},
74+
})
75+
// Check error
76+
assert.NoError(t, err)
77+
assert.Empty(t, apitest.ListUnmatchedRequests())
78+
})
79+
80+
t.Run("handles concurrent deploy", func(t *testing.T) {
81+
// Setup mock api
82+
defer gock.OffAll()
83+
gock.New(mockApiHost).
84+
Get("/v1/projects/" + mockProject + "/functions").
85+
Reply(http.StatusOK).
86+
JSON([]api.FunctionResponse{})
87+
gock.New(mockApiHost).
88+
Post("/v1/projects/" + mockProject + "/functions").
89+
Reply(http.StatusBadRequest).
90+
BodyString("Duplicated function slug")
91+
gock.New(mockApiHost).
92+
Patch("/v1/projects/" + mockProject + "/functions/test").
93+
Reply(http.StatusOK).
94+
JSON(api.FunctionResponse{Slug: "test"})
95+
// Run test
96+
err := client.UpsertFunctions(context.Background(), config.FunctionConfig{
97+
"test": {},
98+
})
99+
// Check error
100+
assert.NoError(t, err)
101+
assert.Empty(t, apitest.ListUnmatchedRequests())
102+
})
103+
45104
t.Run("retries on network failure", func(t *testing.T) {
46105
// Setup mock api
47106
defer gock.OffAll()
@@ -84,6 +143,7 @@ func TestUpsertFunctions(t *testing.T) {
84143
})
85144
// Check error
86145
assert.NoError(t, err)
146+
assert.Empty(t, apitest.ListUnmatchedRequests())
87147
})
88148

89149
t.Run("retries on update failure", func(t *testing.T) {
@@ -109,5 +169,6 @@ func TestUpsertFunctions(t *testing.T) {
109169
})
110170
// Check error
111171
assert.NoError(t, err)
172+
assert.Empty(t, apitest.ListUnmatchedRequests())
112173
})
113174
}

0 commit comments

Comments
 (0)