|
3 | 3 | # Licensed under a 3-clause BSD style license (see LICENSE) |
4 | 4 |
|
5 | 5 | import time |
6 | | -from unittest.mock import Mock, patch |
| 6 | +from unittest.mock import MagicMock, Mock, patch |
7 | 7 |
|
8 | | -from datadog_checks.postgres.connection_pool import AWSTokenProvider, AzureTokenProvider, TokenProvider |
| 8 | +from datadog_checks.postgres.connection_pool import ( |
| 9 | + AWSTokenProvider, |
| 10 | + AzureTokenProvider, |
| 11 | + LRUConnectionPoolManager, |
| 12 | + PostgresConnectionArgs, |
| 13 | + TokenProvider, |
| 14 | +) |
9 | 15 |
|
10 | 16 |
|
11 | 17 | def test_get_token_first_call(): |
@@ -256,6 +262,109 @@ def test_azure_token_provider_integration(): |
256 | 262 | assert mock_credential.get_token.call_count == 1 |
257 | 263 |
|
258 | 264 |
|
| 265 | +def test_multiple_connection_pools_no_token_collision(): |
| 266 | + """ |
| 267 | + Test that multiple LRUConnectionPoolManager instances with different token providers |
| 268 | + don't have token collision issues. |
| 269 | + """ |
| 270 | + # Create two different mock token providers that return different tokens |
| 271 | + token_provider_1 = MockTokenProvider() |
| 272 | + token_provider_1._fetch_token = Mock(return_value=("token_for_rds_1", time.time() + 3600)) |
| 273 | + |
| 274 | + token_provider_2 = MockTokenProvider() |
| 275 | + token_provider_2._fetch_token = Mock(return_value=("token_for_rds_2", time.time() + 3600)) |
| 276 | + |
| 277 | + # Create connection args for two different RDS instances |
| 278 | + conn_args_1 = PostgresConnectionArgs( |
| 279 | + application_name="test_rds_1", |
| 280 | + username="user1", |
| 281 | + host="rds-instance-1.amazonaws.com", |
| 282 | + port=5432, |
| 283 | + ) |
| 284 | + |
| 285 | + conn_args_2 = PostgresConnectionArgs( |
| 286 | + application_name="test_rds_2", |
| 287 | + username="user2", |
| 288 | + host="rds-instance-2.amazonaws.com", |
| 289 | + port=5432, |
| 290 | + ) |
| 291 | + |
| 292 | + # Create two connection pool managers with different token providers |
| 293 | + pool_manager_1 = LRUConnectionPoolManager( |
| 294 | + max_db=2, |
| 295 | + base_conn_args=conn_args_1, |
| 296 | + token_provider=token_provider_1, |
| 297 | + ) |
| 298 | + |
| 299 | + pool_manager_2 = LRUConnectionPoolManager( |
| 300 | + max_db=2, |
| 301 | + base_conn_args=conn_args_2, |
| 302 | + token_provider=token_provider_2, |
| 303 | + ) |
| 304 | + |
| 305 | + # Mock the ConnectionPool to capture what connection_class is passed |
| 306 | + # and simulate connections to verify the token provider behavior |
| 307 | + captured_pools = [] |
| 308 | + |
| 309 | + def mock_ConnectionPool(*args, **pool_kwargs): |
| 310 | + # Capture the kwargs and connection_class |
| 311 | + connection_class = pool_kwargs.get('connection_class') |
| 312 | + conn_kwargs = pool_kwargs.get('kwargs', {}) |
| 313 | + |
| 314 | + captured_pools.append( |
| 315 | + { |
| 316 | + 'connection_class': connection_class, |
| 317 | + 'kwargs': conn_kwargs.copy() if conn_kwargs else {}, |
| 318 | + } |
| 319 | + ) |
| 320 | + |
| 321 | + # Create a mock pool |
| 322 | + mock_pool = MagicMock() |
| 323 | + return mock_pool |
| 324 | + |
| 325 | + with patch('datadog_checks.postgres.connection_pool.ConnectionPool', side_effect=mock_ConnectionPool): |
| 326 | + # Create pools |
| 327 | + pool_manager_1._create_pool("db1") |
| 328 | + pool_manager_2._create_pool("db2") |
| 329 | + |
| 330 | + # Verify that pools were created |
| 331 | + assert len(captured_pools) == 2, "Should have created 2 connection pools" |
| 332 | + |
| 333 | + # Get the pools for each RDS instance |
| 334 | + pool_1 = captured_pools[0] |
| 335 | + pool_2 = captured_pools[1] |
| 336 | + |
| 337 | + # Simulate what happens when a connection is established by calling the connect method |
| 338 | + # This will trigger the token provider logic |
| 339 | + conn_class_1 = pool_1['connection_class'] |
| 340 | + conn_class_2 = pool_2['connection_class'] |
| 341 | + |
| 342 | + # Call connect to see what token each would use |
| 343 | + # We need to mock the parent connect to capture the password |
| 344 | + with patch('psycopg.Connection.connect') as mock_parent_connect: |
| 345 | + mock_parent_connect.return_value = MagicMock() |
| 346 | + |
| 347 | + # Call connect for pool 1 with its kwargs |
| 348 | + conn_class_1.connect(**pool_1['kwargs']) |
| 349 | + password_1 = mock_parent_connect.call_args[1].get('password') |
| 350 | + |
| 351 | + # Call connect for pool 2 with its kwargs |
| 352 | + conn_class_2.connect(**pool_2['kwargs']) |
| 353 | + password_2 = mock_parent_connect.call_args[1].get('password') |
| 354 | + |
| 355 | + # Verify no collision - each pool manager uses its own token provider |
| 356 | + assert password_1 == "token_for_rds_1" |
| 357 | + assert password_2 == "token_for_rds_2" |
| 358 | + |
| 359 | + # Verify each token provider was called independently |
| 360 | + assert token_provider_1._fetch_token.call_count >= 1, "Token provider 1 should be called at least once" |
| 361 | + assert token_provider_2._fetch_token.call_count >= 1, "Token provider 2 should be called at least once" |
| 362 | + |
| 363 | + # Clean up |
| 364 | + pool_manager_1.close_all() |
| 365 | + pool_manager_2.close_all() |
| 366 | + |
| 367 | + |
259 | 368 | class MockTokenProvider(TokenProvider): |
260 | 369 | """Mock implementation of TokenProvider for testing.""" |
261 | 370 |
|
|
0 commit comments