Skip to content

Commit a14d448

Browse files
authored
fix: fixing a bug where exclusion list was not honored before falling back to global endpoint. (#43297)
* fix: fixing a bug where exclusion list was not honored before falling back to global endpoint. * fix: updated changelog * fix: addressing comments
1 parent 805a635 commit a14d448

File tree

3 files changed

+100
-8
lines changed

3 files changed

+100
-8
lines changed

sdk/cosmos/azure-cosmos/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
* Changed `retry_write` from `bool` to `int` to match other retryable options. See [PR 43341](https://github.com/Azure/azure-sdk-for-python/pull/43341).
1111

1212
#### Bugs Fixed
13-
13+
* Fixed bug where exclusion list was not honored before falling back to global endpoint for multi-write region accounts. See[PR 43297](https://github.com/Azure/azure-sdk-for-python/pull/43297)
14+
1415
#### Other Changes
1516
* Removed dual endpoint tracking from the sdk. See [PR 40451](https://github.com/Azure/azure-sdk-for-python/pull/40451).
1617
* Reverted typehints to fix the mismatch issue. See [PR 43124](https://github.com/Azure/azure-sdk-for-python/pull/43124)

sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def _get_applicable_regional_routing_contexts(regional_routing_contexts: list[Re
105105
if base.IsMasterResource(resource_type):
106106
applicable_regional_routing_contexts.extend(excluded_regional_routing_contexts)
107107

108-
# If applicable_regional_routing_contexts is empty add fallback regional routing context
108+
# If all preferred locations are excluded, use the fallback endpoint.
109109
if not applicable_regional_routing_contexts:
110110
applicable_regional_routing_contexts.append(fall_back_regional_routing_context)
111111

@@ -397,19 +397,22 @@ def get_preferred_regional_routing_contexts(
397397
# can use multiple write locations, preferred locations list should be
398398
# used for determining both read and write endpoints order.
399399
for location in self.effective_preferred_locations:
400-
regional_endpoint = endpoints_by_location[location] if location in endpoints_by_location \
401-
else None
400+
regional_endpoint = endpoints_by_location.get(location)
402401
if regional_endpoint:
403402
if self.is_endpoint_unavailable(regional_endpoint.get_primary(),
404403
expected_available_operation):
405404
unavailable_endpoints.append(regional_endpoint)
406405
else:
407406
regional_endpoints.append(regional_endpoint)
408407

408+
# If all preferred locations are unavailable, honor the preferred list by trying them anyway.
409+
if not regional_endpoints and unavailable_endpoints:
410+
regional_endpoints.extend(unavailable_endpoints)
411+
412+
# If there are no preferred locations or none of the preferred locations are in the account,
413+
# add the fallback endpoint.
409414
if not regional_endpoints:
410415
regional_endpoints.append(fallback_endpoint)
411-
412-
regional_endpoints.extend(unavailable_endpoints)
413416
else:
414417
for location in orderedLocations:
415418
if location and location in endpoints_by_location:

sdk/cosmos/azure-cosmos/tests/test_location_cache.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,10 @@ def test_resolve_request_endpoint_preferred_regions(self):
146146
lc.mark_endpoint_unavailable_for_write(location3_endpoint, True)
147147
read_resolved = lc.resolve_service_endpoint(read_doc_request)
148148
write_resolved = lc.resolve_service_endpoint(write_doc_request)
149-
assert read_resolved == write_resolved
150-
assert read_resolved == default_endpoint
149+
# With the updated logic, we should retry the unavailable preferred locations
150+
# instead of falling back to the default endpoint.
151+
assert read_resolved == location1_endpoint
152+
assert write_resolved == location1_endpoint
151153

152154
@pytest.mark.parametrize("test_type",["OnClient", "OnRequest", "OnBoth"])
153155
def test_get_applicable_regional_endpoints_excluded_regions(self, test_type):
@@ -266,6 +268,92 @@ def test_set_excluded_locations_for_requests(self):
266268
assert str(
267269
e.value) == expected_error_message
268270

271+
def test_resolve_endpoint_unavailable_and_excluded_preferred_regions(self):
272+
# Scenario: All preferred read locations are unavailable AND in the excluded list.
273+
# Expected: Fallback to the primary write region.
274+
connection_policy = documents.ConnectionPolicy()
275+
connection_policy.ExcludedLocations = [location1_name, location4_name]
276+
lc = refresh_location_cache([location1_name, location4_name], True, connection_policy)
277+
db_acc = create_database_account(True)
278+
lc.perform_on_database_account_read(db_acc)
279+
280+
# Mark all preferred read locations as unavailable
281+
lc.mark_endpoint_unavailable_for_read(location1_endpoint, True)
282+
lc.mark_endpoint_unavailable_for_read(location4_endpoint, True)
283+
284+
# Create a read request
285+
read_doc_request = RequestObject(ResourceType.Document, _OperationType.Read, None)
286+
287+
# Resolve the endpoint for the read request
288+
read_doc_resolved = lc.resolve_service_endpoint(read_doc_request)
289+
290+
# All preferred read locations ([loc1, loc4]) are excluded.
291+
# The fallback for read is the primary write region, which is loc1.
292+
assert read_doc_resolved == location1_endpoint
293+
294+
# Scenario: All preferred write locations are unavailable AND in the excluded list.
295+
# Expected: Fallback to the default endpoint.
296+
connection_policy.ExcludedLocations = [location1_name, location2_name]
297+
lc = refresh_location_cache([location1_name, location2_name], True, connection_policy)
298+
db_acc = create_database_account(True)
299+
lc.perform_on_database_account_read(db_acc)
300+
301+
# Mark preferred write locations as unavailable
302+
lc.mark_endpoint_unavailable_for_write(location1_endpoint, True)
303+
lc.mark_endpoint_unavailable_for_write(location2_endpoint, True)
304+
305+
# Create a write request
306+
write_doc_request = RequestObject(ResourceType.Document, _OperationType.Create, None)
307+
308+
# Resolve the endpoint for the write request
309+
write_doc_resolved = lc.resolve_service_endpoint(write_doc_request)
310+
311+
# All preferred write locations ([loc1, loc2]) are excluded.
312+
# The fallback for write is the default_endpoint.
313+
assert write_doc_resolved == default_endpoint
314+
315+
def test_resolve_endpoint_unavailable_and_excluded_on_request(self):
316+
# Scenario: All preferred read locations are unavailable AND in the excluded list on the request.
317+
# Expected: Fallback to the primary write region.
318+
lc = refresh_location_cache([location1_name, location4_name], True)
319+
db_acc = create_database_account(True)
320+
lc.perform_on_database_account_read(db_acc)
321+
322+
# Mark all preferred read locations as unavailable
323+
lc.mark_endpoint_unavailable_for_read(location1_endpoint, True)
324+
lc.mark_endpoint_unavailable_for_read(location4_endpoint, True)
325+
326+
# Create a read request and set excluded locations
327+
read_doc_request = RequestObject(ResourceType.Document, _OperationType.Read, None)
328+
read_doc_request.excluded_locations = [location1_name, location4_name]
329+
330+
# Resolve the endpoint for the read request
331+
read_doc_resolved = lc.resolve_service_endpoint(read_doc_request)
332+
333+
# All preferred read locations ([loc1, loc4]) are excluded.
334+
# The fallback for read is the primary write region, which is loc1.
335+
assert read_doc_resolved == location1_endpoint
336+
337+
# Scenario: All preferred write locations are unavailable AND in the excluded list on the request.
338+
# Expected: Fallback to the default endpoint.
339+
lc = refresh_location_cache([location1_name, location2_name], True)
340+
db_acc = create_database_account(True)
341+
lc.perform_on_database_account_read(db_acc)
342+
343+
# Mark preferred write locations as unavailable
344+
lc.mark_endpoint_unavailable_for_write(location1_endpoint, True)
345+
lc.mark_endpoint_unavailable_for_write(location2_endpoint, True)
346+
347+
# Create a write request and set excluded locations
348+
write_doc_request = RequestObject(ResourceType.Document, _OperationType.Create, None)
349+
write_doc_request.excluded_locations = [location1_name, location2_name]
350+
351+
# Resolve the endpoint for the write request
352+
write_doc_resolved = lc.resolve_service_endpoint(write_doc_request)
353+
354+
# All preferred write locations ([loc1, loc2]) are excluded.
355+
# The fallback for write is the default_endpoint.
356+
assert write_doc_resolved == default_endpoint
269357

270358
if __name__ == "__main__":
271359
unittest.main()

0 commit comments

Comments
 (0)