Skip to content

Commit 7ce1917

Browse files
authored
Fix Client Settings issue with missing available regions (#40763)
* Fixed Client Available Regions in logging policy * Added emulator tests for client setting in logs * Disabled pylint errors
1 parent 6f2720c commit 7ce1917

File tree

4 files changed

+118
-25
lines changed

4 files changed

+118
-25
lines changed

sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_http_logging_policy.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@
3434
from azure.core.pipeline import PipelineRequest, PipelineResponse
3535
from azure.core.pipeline.policies import HttpLoggingPolicy
3636

37+
from ._location_cache import LocationCache
3738
from .http_constants import HttpHeaders
3839
from ._global_endpoint_manager import _GlobalEndpointManager
39-
from .documents import DatabaseAccount
40+
from .documents import DatabaseAccount, ConnectionPolicy
4041

4142
if TYPE_CHECKING:
4243
from azure.core.rest import HttpRequest, HttpResponse, AsyncHttpResponse
@@ -215,7 +216,7 @@ def on_request(
215216
logger.info(log_string, extra=cosmos_logger_attributes)
216217

217218
except Exception as err: # pylint: disable=broad-except
218-
logger.warning("Failed to log request: %s", repr(err))
219+
logger.warning("Failed to log request: %s", repr(err)) #pylint: disable=do-not-log-exceptions-if-not-debug
219220
return
220221
super().on_request(request)
221222

@@ -299,25 +300,32 @@ def on_response( # pylint: disable=too-many-statements, too-many-branches
299300
log_string += "\nResponse error message: {}".format(_format_error(http_response.text()))
300301
logger.info(log_string, extra=log_data)
301302
except Exception as err: # pylint: disable=broad-except
302-
logger.warning("Failed to log response: %s", repr(err), extra=log_data)
303+
logger.warning("Failed to log response: %s", repr(err), extra=log_data) #pylint: disable=do-not-log-exceptions-if-not-debug
303304
return
304305
super().on_response(request, response)
305306

306307
def __get_client_settings(self) -> Optional[Dict[str, Any]]:
307308
# Place any client settings we want to log here
308-
client_info: Dict[str, Any] = {"Client Preferred Regions": [], "Client Available Read Regions": [],
309-
"Client Available Write Regions": []}
309+
client_preferred_regions = []
310+
client_excluded_regions = []
311+
client_account_read_regions = []
312+
client_account_write_regions = []
313+
310314
if self.__global_endpoint_manager:
311-
client_info['Client Preferred Regions'] = self.__global_endpoint_manager.PreferredLocations \
312-
if hasattr(self.__global_endpoint_manager, "PreferredLocations") else []
313-
try:
314-
location_cache = self.__global_endpoint_manager.location_cache
315-
client_info["Client Available Read Regions"] = location_cache.available_read_locations
316-
client_info["Client Available Write Regions"] = location_cache.available_write_locations
317-
except AttributeError:
318-
client_info["Client Available Read Regions"] = []
319-
client_info["Client Available Write Regions"] = []
320-
return client_info
315+
if self.__global_endpoint_manager.Client and self.__global_endpoint_manager.Client.connection_policy:
316+
connection_policy: ConnectionPolicy = self.__global_endpoint_manager.Client.connection_policy
317+
client_preferred_regions = connection_policy.PreferredLocations
318+
client_excluded_regions = connection_policy.ExcludedLocations
319+
320+
if self.__global_endpoint_manager.location_cache:
321+
location_cache: LocationCache = self.__global_endpoint_manager.location_cache
322+
client_account_read_regions = location_cache.account_read_locations
323+
client_account_write_regions = location_cache.account_write_locations
324+
325+
return {"Client Preferred Regions": client_preferred_regions,
326+
"Client Excluded Regions": client_excluded_regions,
327+
"Client Account Read Regions": client_account_read_regions,
328+
"Client Account Write Regions": client_account_write_regions}
321329

322330
def __get_database_account_settings(self) -> Optional[DatabaseAccount]:
323331
if self.__global_endpoint_manager and hasattr(self.__global_endpoint_manager, '_database_account_cache'):

sdk/cosmos/azure-cosmos/azure/cosmos/aio/_global_endpoint_manager_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ async def refresh_endpoint_list(self, database_account, **kwargs):
103103
await self.refresh_task
104104
self.refresh_task = None
105105
except (Exception, asyncio.CancelledError) as exception: #pylint: disable=broad-exception-caught
106-
logger.exception("Health check task failed: %s", exception)
106+
logger.exception("Health check task failed: %s", exception) #pylint: disable=do-not-use-logging-exception
107107
if self.location_cache.current_time_millis() - self.last_refresh_time > self.refresh_time_interval_in_ms:
108108
self.refresh_needed = True
109109
if self.refresh_needed:

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

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
import pytest
1111

1212
import azure.cosmos.cosmos_client as cosmos_client
13+
from azure.cosmos.container import ContainerProxy
1314
import test_config
15+
from _fault_injection_transport import FaultInjectionTransport
16+
from test_fault_injection_transport import TestFaultInjectionTransport
17+
from typing import List, Callable
18+
from azure.core.rest import HttpRequest
1419

1520
try:
1621
from unittest.mock import Mock
@@ -30,8 +35,32 @@ def reset(self):
3035
def emit(self, record):
3136
self.messages.append(record)
3237

33-
34-
38+
CONFIG = test_config.TestConfig
39+
L1 = "Location1"
40+
L2 = "Location2"
41+
L1_URL = test_config.TestConfig.local_host
42+
L2_URL = L1_URL.replace("localhost", "127.0.0.1")
43+
URL_TO_LOCATIONS = {
44+
L1_URL: L1,
45+
L2_URL: L2}
46+
47+
48+
def create_logger(name: str, mock_handler: MockHandler, level: int = logging.INFO) -> logging.Logger:
49+
logger = logging.getLogger(name)
50+
logger.addHandler(mock_handler)
51+
logger.setLevel(level)
52+
53+
return logger
54+
55+
def get_locations_list(msg: str) -> List[str]:
56+
msg = msg.replace(' ', '')
57+
msg = msg.replace('\'', '')
58+
# Find the substring between the first '[' and the last ']'
59+
start = msg.find('[') + 1
60+
end = msg.rfind(']')
61+
# Extract the substring and convert it to a list using ast.literal_eval
62+
msg = msg[start:end]
63+
return msg.split(',')
3564

3665
@pytest.mark.cosmosEmulator
3766
class TestCosmosHttpLogger(unittest.TestCase):
@@ -54,12 +83,8 @@ def setUpClass(cls):
5483
"tests.")
5584
cls.mock_handler_default = MockHandler()
5685
cls.mock_handler_diagnostic = MockHandler()
57-
cls.logger_default = logging.getLogger("testloggerdefault")
58-
cls.logger_default.addHandler(cls.mock_handler_default)
59-
cls.logger_default.setLevel(logging.INFO)
60-
cls.logger_diagnostic = logging.getLogger("testloggerdiagnostic")
61-
cls.logger_diagnostic.addHandler(cls.mock_handler_diagnostic)
62-
cls.logger_diagnostic.setLevel(logging.INFO)
86+
cls.logger_default = create_logger("testloggerdefault", cls.mock_handler_default)
87+
cls.logger_diagnostic = create_logger("testloggerdiagnostic", cls.mock_handler_diagnostic)
6388
cls.client_default = cosmos_client.CosmosClient(cls.host, cls.masterKey,
6489
consistency_level="Session",
6590
connection_policy=cls.connectionPolicy,
@@ -136,6 +161,65 @@ def test_cosmos_http_logging_policy(self):
136161

137162
self.mock_handler_diagnostic.reset()
138163

164+
def test_client_settings(self):
165+
# Test data
166+
all_locations = [L1, L2]
167+
client_excluded_locations = [L1]
168+
multiple_write_locations = True
169+
170+
# Client setup
171+
mock_handler = MockHandler()
172+
logger = create_logger("test_logger_client_settings", mock_handler)
173+
174+
custom_transport = FaultInjectionTransport()
175+
is_get_account_predicate: Callable[[HttpRequest], bool] = lambda \
176+
r: FaultInjectionTransport.predicate_is_database_account_call(r)
177+
emulator_as_multi_write_region_account_transformation = \
178+
lambda r, inner: FaultInjectionTransport.transform_topology_mwr(
179+
first_region_name=L1,
180+
second_region_name=L2,
181+
inner=inner,
182+
first_region_url=L1_URL,
183+
second_region_url=L2_URL,
184+
)
185+
custom_transport.add_response_transformation(
186+
is_get_account_predicate,
187+
emulator_as_multi_write_region_account_transformation)
188+
189+
initialized_objects = TestFaultInjectionTransport.setup_method_with_custom_transport(
190+
custom_transport,
191+
default_endpoint=CONFIG.host,
192+
key=CONFIG.masterKey,
193+
database_id=CONFIG.TEST_DATABASE_ID,
194+
container_id=CONFIG.TEST_SINGLE_PARTITION_CONTAINER_ID,
195+
preferred_locations=all_locations,
196+
excluded_locations=client_excluded_locations,
197+
multiple_write_locations=multiple_write_locations,
198+
custom_logger=logger
199+
)
200+
mock_handler.reset()
201+
202+
# create an item
203+
id_value: str = str(uuid.uuid4())
204+
document_definition = {'id': id_value, 'pk': id_value}
205+
container: ContainerProxy = initialized_objects["col"]
206+
container.create_item(body=document_definition)
207+
208+
# Verify endpoint locations
209+
messages_split = mock_handler.messages[1].message.split("\n")
210+
for message in messages_split:
211+
if "Client Preferred Regions:" in message:
212+
locations = get_locations_list(message)
213+
assert all_locations == locations
214+
elif "Client Excluded Regions:" in message:
215+
locations = get_locations_list(message)
216+
assert client_excluded_locations == locations
217+
elif "Client Account Read Regions:" in message:
218+
locations = get_locations_list(message)
219+
assert all_locations == locations
220+
elif "Client Account Write Regions:" in message:
221+
locations = get_locations_list(message)
222+
assert all_locations == locations
139223

140224
if __name__ == "__main__":
141225
unittest.main()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ def setup_method_with_custom_transport(
6868
key: str = master_key,
6969
database_id: str = TEST_DATABASE_ID,
7070
container_id: str = SINGLE_PARTITION_CONTAINER_NAME,
71+
custom_logger = logger,
7172
**kwargs):
7273
client = CosmosClient(default_endpoint, key, consistency_level="Session",
73-
transport=custom_transport, logger=logger, enable_diagnostics_logging=True, **kwargs)
74+
transport=custom_transport, logger=custom_logger, enable_diagnostics_logging=True, **kwargs)
7475
db: DatabaseProxy = client.get_database_client(database_id)
7576
container: ContainerProxy = db.get_container_client(container_id)
7677
return {"client": client, "db": db, "col": container}

0 commit comments

Comments
 (0)