Skip to content

Commit a63ea12

Browse files
Drvinickrobinson251
authored andcommitted
Add tests for retry logic based on response status code
1 parent 8db6c5c commit a63ea12

File tree

4 files changed

+149
-6
lines changed

4 files changed

+149
-6
lines changed

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ version = "0.1.0"
44

55
[compat]
66
CloudBase = "1"
7+
HTTP = "1"
78
ReTestItems = "1"
9+
Sockets = "1"
810
Test = "1"
911
julia = "1.8"
1012

1113
[extras]
1214
CloudBase = "85eb1798-d7c4-4918-bb13-c944d38e27ed"
15+
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
1316
ReTestItems = "817f1d60-ba6b-4fd5-9520-3cf149f6a823"
17+
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
1418
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1519

1620
[targets]
17-
test = ["CloudBase", "ReTestItems", "Test"]
21+
test = ["CloudBase", "HTTP", "ReTestItems", "Sockets", "Test"]

test/azure_blobs_exception_tests.jl

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
@test false # Should have thrown an error
4242
catch e
4343
@test e isa ErrorException
44-
@test occursin("400 Bad Request", e.msg)
44+
@test occursin("400 Bad Request", e.msg) # Should this be 403 Forbidden? We've seen that with invalid SAS tokens
4545
@test occursin("Authentication information is not given in the correct format", e.msg)
4646
end
4747

@@ -151,3 +151,141 @@
151151
@test res == 1 # Rust CResult::Error
152152
end
153153
end # @testitem
154+
155+
@testitem "BlobStorage retries" setup=[InitializeRustStore] begin
156+
using CloudBase.CloudTest: Azurite
157+
import CloudBase
158+
using ObjectStore: blob_get!, blob_put, AzureCredentials
159+
import HTTP
160+
import Sockets
161+
162+
max_retries = InitializeRustStore.max_retries
163+
164+
function test_status(method, response_status, headers=nothing)
165+
@assert method === :GET || method === :PUT
166+
nretries = Ref(0)
167+
response_body = "response body from the dummy server"
168+
account = "myaccount"
169+
container = "mycontainer"
170+
shared_key_from_azurite = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
171+
172+
(port, tcp_server) = Sockets.listenany(8081)
173+
http_server = HTTP.serve!(tcp_server) do request::HTTP.Request
174+
if request.method == "GET" && request.target == "/$account/$container/_this_file_does_not_exist"
175+
# This is the exploratory ping from connect_and_test in lib.rs
176+
return HTTP.Response(404, "Yup, still doesn't exist")
177+
end
178+
nretries[] += 1
179+
response = isnothing(headers) ? HTTP.Response(response_status, response_body) : HTTP.Response(response_status, headers, response_body)
180+
return response
181+
end
182+
183+
baseurl = "http://127.0.0.1:$port/$account/$container/"
184+
creds = AzureCredentials(account, container, shared_key_from_azurite, baseurl)
185+
186+
try
187+
method === :GET && blob_get!(joinpath(baseurl, "blob"), zeros(UInt8, 5), creds)
188+
method === :PUT && blob_put(joinpath(baseurl, "blob"), codeunits("a,b,c"), creds)
189+
@test false # Should have thrown an error
190+
catch e
191+
@test e isa ErrorException
192+
@test occursin(string(response_status), e.msg)
193+
response_status < 500 && (@test occursin("response body from the dummy server", e.msg))
194+
finally
195+
close(http_server)
196+
end
197+
wait(http_server)
198+
return nretries[]
199+
end
200+
201+
# See https://learn.microsoft.com/en-us/rest/api/searchservice/http-status-codes
202+
203+
@testset "400: Bad Request" begin
204+
# Returned when there's an error in the request URI, headers, or body. The response body
205+
# contains an error message explaining what the specific problem is.
206+
nretries = test_status(:GET, 400)
207+
@test nretries == 1 broken=true
208+
nretries = test_status(:PUT, 400)
209+
@test nretries == 1 broken=true
210+
end
211+
212+
@testset "403: Forbidden" begin
213+
# Returned when you pass an invalid api-key.
214+
nretries = test_status(:GET, 403)
215+
@test nretries == 1 broken=true
216+
nretries = test_status(:PUT, 403)
217+
@test nretries == 1 broken=true
218+
end
219+
220+
@testset "404: Not Found" begin
221+
nretries = test_status(:GET, 404)
222+
@test nretries == 1
223+
end
224+
225+
@testset "405: Method Not Supported" begin
226+
nretries = test_status(:GET, 405, ["Allow" => "PUT"])
227+
@test nretries == 1 broken=true
228+
nretries = test_status(:PUT, 405, ["Allow" => "GET"])
229+
@test nretries == 1 broken=true
230+
end
231+
232+
@testset "409: Conflict" begin
233+
# Returned when write operations conflict.
234+
# NOTE: We currently don't retry but maybe we should? This is probably a case where the
235+
# retry logic should add more noise to the backoff so that multiple writers don't collide on retry.
236+
nretries = test_status(:GET, 409)
237+
@test nretries == max_retries broken=true
238+
nretries = test_status(:PUT, 409)
239+
@test nretries == max_retries broken=true
240+
end
241+
242+
@testset "412: Precondition Failed" begin
243+
# Returned when an If-Match or If-None-Match header's condition evaluates to false
244+
nretries = test_status(:GET, 412)
245+
@test nretries == 1
246+
nretries = test_status(:PUT, 412)
247+
@test nretries == 1
248+
end
249+
250+
@testset "413: Content Too Large" begin
251+
# https://learn.microsoft.com/en-us/rest/api/storageservices/put-blob?tabs=shared-access-signatures#remarks
252+
nretries = test_status(:PUT, 413)
253+
@test nretries == 1 broken=true
254+
end
255+
256+
@testset "429: Too Many Requests" begin
257+
# TODO: We probably should respect the Retry-After header, but we currently don't
258+
# (and we don't know if Azure actually sets it)
259+
# NOTE: This can happen when Azure is throttling us, so it might be a good idea to retry with some
260+
# larger initial backoff (very eager retries probably only make the situation worse).
261+
nretries = test_status(:GET, 429, ["Retry-After" => 10])
262+
@test nretries == max_retries broken=true
263+
nretries = test_status(:PUT, 429, ["Retry-After" => 10])
264+
@test nretries == max_retries broken=true
265+
end
266+
267+
@testset "502: Bad Gateway" begin
268+
# This error occurs when you enter HTTP instead of HTTPS in the connection.
269+
nretries = test_status(:GET, 502)
270+
@test nretries == 1 broken=true
271+
nretries = test_status(:PUT, 502)
272+
@test nretries == 1 broken=true
273+
end
274+
275+
@testset "503: Service Unavailable" begin
276+
# NOTE: This seems similar to 429 and the Azure docs specifically say:
277+
# Important: In this case, we highly recommend that your client code back off and wait before retrying
278+
nretries = test_status(:GET, 503)
279+
@test nretries == max_retries broken=true
280+
nretries = test_status(:PUT, 503)
281+
@test nretries == max_retries broken=true
282+
end
283+
284+
@testset "504: Gateway Timeout" begin
285+
# Azure AI Search listens on HTTPS port 443. If your search service URL contains HTTP instead of HTTPS, a 504 status code is returned.
286+
nretries = test_status(:GET, 504)
287+
@test nretries == 1 broken=true
288+
nretries = test_status(:PUT, 504)
289+
@test nretries == 1 broken=true
290+
end
291+
end

test/common_testsetup.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
using ObjectStore
33
# Since we currently only support centralized configs, we need to have one that is compatible
44
# with all the tests (some of the tests would take too long if we use default values).
5-
max_retries = 5
6-
retry_timeout_sec = 5
5+
max_retries = 2
6+
retry_timeout_sec = 2
7+
ObjectStore.init_rust_store(ObjectStore.RustStoreConfig(max_retries, retry_timeout_sec))
78
init_object_store(ObjectStoreConfig(max_retries, retry_timeout_sec))
89
end

test/runtests.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ using ReTestItems
22
using ObjectStore
33

44
withenv("RUST_BACKTRACE"=>1) do
5-
runtests(ObjectStore, testitem_timeout=120, nworkers=1)
6-
end
5+
runtests(ObjectStore, testitem_timeout=180, nworkers=1)
6+
end

0 commit comments

Comments
 (0)