9
9
import logging
10
10
import uuid
11
11
import warnings
12
- import datetime
12
+ import subprocess
13
+ import time
14
+ import signal
15
+ import ssl
16
+ import functools
17
+
18
+ from typing import Callable
19
+
13
20
from functools import partial
14
21
from logging .handlers import RotatingFileHandler
15
22
from azure .core .settings import settings
45
52
EVENTHUB_DEFAULT_AUTH_RULE_NAME = "RootManageSharedAccessKey"
46
53
LOCATION = get_region_override ("westus" )
47
54
55
+ # Set up the amqpproxy environment variables
56
+ path = os .environ .get ("AMQPPROXY_PATH" )
57
+ AMQPPROXY_PATH = os .path .abspath (path ) if path else None
58
+ RECORD_AMQP_PROXY = os .environ .get ("RECORD_AMQP_PROXY" ) == 'true'
59
+ AMQPPROXY_RECORDINGS_DIR = os .path .join (os .path .dirname (__file__ ), "amqpproxy_recordings" )
60
+ if RECORD_AMQP_PROXY :
61
+ if not os .path .exists (AMQPPROXY_RECORDINGS_DIR ):
62
+ os .makedirs (AMQPPROXY_RECORDINGS_DIR )
63
+
64
+ # Create/overwrite the amqp proxy startup log file
65
+ AMQPPROXY_STARTUP_LOG = os .path .join (AMQPPROXY_RECORDINGS_DIR , "amqpproxy_startup.log" )
66
+ # Create/overwrite the amqp proxy startup log file
67
+ if os .path .exists (AMQPPROXY_STARTUP_LOG ):
68
+ with open (AMQPPROXY_STARTUP_LOG , "w" ) as log_file :
69
+ log_file .write ("" ) # Overwrite the file with an empty string
70
+ else :
71
+ open (AMQPPROXY_STARTUP_LOG , "w" ).close () # Create the file if it doesn't exist
72
+
73
+ AMQPPROXY_CUSTOM_ENDPOINT_ADDRESS = "sb://localhost:5671"
74
+ AMQPPROXY_TRANSPORT_TYPE = TransportType .Amqp
75
+
76
+ context = ssl .SSLContext (ssl .PROTOCOL_TLS_CLIENT )
77
+ context .check_hostname = False
78
+ context .verify_mode = ssl .CERT_NONE
79
+ AMQPPROXY_SSL_CONTEXT = context
80
+ AMQPPROXY_CLIENT_ARGS = {
81
+ "custom_endpoint_address" : AMQPPROXY_CUSTOM_ENDPOINT_ADDRESS ,
82
+ "ssl_context" : AMQPPROXY_SSL_CONTEXT ,
83
+ "transport_type" : AMQPPROXY_TRANSPORT_TYPE ,
84
+ }
85
+
48
86
49
87
def pytest_addoption (parser ):
50
88
parser .addoption ("--sleep" , action = "store" , default = "True" , help = "sleep on reconnect test: True or False" )
@@ -245,6 +283,105 @@ def connection_str(live_eventhub):
245
283
live_eventhub ["hostname" ], live_eventhub ["key_name" ], live_eventhub ["access_key" ], live_eventhub ["event_hub" ]
246
284
)
247
285
286
+ @pytest .fixture ()
287
+ def skip_amqp_proxy (request ):
288
+ """Helper method to determine if the AMQP proxy should be run for a test."""
289
+ if not RECORD_AMQP_PROXY or not AMQPPROXY_PATH :
290
+ return True
291
+ if any (marker .name == "no_amqpproxy" for marker in request .node .own_markers ):
292
+ return True
293
+ return False
294
+
295
+ @pytest .fixture ()
296
+ def client_args (skip_amqp_proxy ):
297
+ """Fixture that adds the amqpproxy client args to the test context."""
298
+ if skip_amqp_proxy :
299
+ return {}
300
+ # Add proxy args to test context
301
+ return AMQPPROXY_CLIENT_ARGS
302
+
303
+ def remove_existing_recordings (path , file_name ):
304
+ """Remove existing recordings for the test."""
305
+ # Remove any existing recordings for the test
306
+ for file in os .listdir (path ):
307
+ if file .startswith (file_name ) and file .endswith (".json" ):
308
+ os .remove (os .path .join (path , file ))
309
+ print (f"Removed existing recording: { file } " )
310
+
311
+ def stop_existing_amqpproxy (log_file ):
312
+ # Kill any existing process using the AMQP proxy port
313
+ try :
314
+ subprocess .run (
315
+ ["fuser" , "-k" , "5671/tcp" ], check = True , stdout = log_file , stderr = log_file
316
+ )
317
+ log_file .write ("Kill existing process on port 5671.\n " )
318
+ except subprocess .CalledProcessError :
319
+ log_file .write ("No existing process found on port 5671.\n " )
320
+
321
+ @pytest .fixture (autouse = True )
322
+ def amqpproxy (live_eventhub , skip_amqp_proxy , request ):
323
+ """Fixture that redirects network requests to target the amqp proxy.
324
+ Tests can opt out using @pytest.mark.no_amqpproxy or set the environment variable
325
+ RECORD_AMQP_PROXY=False.
326
+ """
327
+ # Skip if not recording or test opted out
328
+ if skip_amqp_proxy :
329
+ yield
330
+ return
331
+
332
+ # Use test name as logfile
333
+ test_name = request .node .name
334
+ # Mirror relative path in AMQPPROXY_RECORDINGS_PATH for recording files
335
+ relative_path = os .path .relpath (request .node .fspath , start = os .path .dirname (__file__ ))
336
+ recording_dir_path = os .path .join (AMQPPROXY_RECORDINGS_DIR , os .path .dirname (relative_path ))
337
+ file_name = os .path .splitext (os .path .basename (request .node .fspath ))[0 ]
338
+ recording_file = f"{ file_name } .{ test_name } "
339
+ if not os .path .exists (recording_dir_path ):
340
+ os .makedirs (recording_dir_path )
341
+ else :
342
+ # Remove any existing recordings with the same test name, so that we overwrite instead of add
343
+ remove_existing_recordings (recording_dir_path , recording_file )
344
+
345
+ # Start amqpproxy process
346
+ log_file = open (AMQPPROXY_STARTUP_LOG , "a" )
347
+ # Flush log after writing to ensure log line ordering is preserved
348
+ log_file .write (f"####### Starting amqpproxy for test: { test_name } \n " )
349
+ log_file .flush ()
350
+ try :
351
+ # Navigate to the amqpproxy directory and run the proxy
352
+ os .chdir (AMQPPROXY_PATH )
353
+ stop_existing_amqpproxy (log_file )
354
+
355
+ # Start the AMQP proxy process
356
+ log_file .write ("Starting amqpproxy process...\n " )
357
+ log_file .flush ()
358
+ proxy_process = subprocess .Popen (
359
+ ["go" , "run" , "." ,
360
+ "--host" , live_eventhub ["hostname" ],
361
+ "--logs" , recording_dir_path ,
362
+ "--logfile" , recording_file ],
363
+ stdout = log_file ,
364
+ stderr = log_file ,
365
+ preexec_fn = os .setsid
366
+ )
367
+
368
+ if not proxy_process :
369
+ log_file .write ("Failed to start amqpproxy.\n " )
370
+ raise RuntimeError (f"Failed to start amqpproxy. Check for errors in { AMQPPROXY_STARTUP_LOG } " )
371
+
372
+ try :
373
+ time .sleep (1 )
374
+ # Add proxy args to test context
375
+ request .node .user_properties .append (("client_args" , AMQPPROXY_CLIENT_ARGS ))
376
+ yield
377
+ finally :
378
+ os .killpg (os .getpgid (proxy_process .pid ), signal .SIGTERM )
379
+ proxy_process .wait ()
380
+ finally :
381
+ log_file .write (f"####### Stopping amqpproxy for test: { test_name } \n " )
382
+ log_file .flush ()
383
+ log_file .close ()
384
+
248
385
249
386
@pytest .fixture ()
250
387
def invalid_hostname (live_eventhub ):
@@ -269,7 +406,7 @@ def invalid_policy(live_eventhub):
269
406
270
407
271
408
@pytest .fixture ()
272
- def connstr_receivers (live_eventhub , uamqp_transport ):
409
+ def connstr_receivers (live_eventhub , uamqp_transport , client_args ):
273
410
connection_str = live_eventhub ["connection_str" ]
274
411
partitions = [str (i ) for i in range (PARTITION_COUNT )]
275
412
receivers = []
@@ -286,7 +423,7 @@ def connstr_receivers(live_eventhub, uamqp_transport):
286
423
else :
287
424
sas_auth = SASTokenAuth (uri , uri , live_eventhub ["key_name" ], live_eventhub ["access_key" ])
288
425
receiver = ReceiveClient (
289
- live_eventhub ["hostname" ], source , auth = sas_auth , network_trace = False , timeout = 0 , link_credit = 500
426
+ live_eventhub ["hostname" ], source , auth = sas_auth , network_trace = False , timeout = 0 , link_credit = 500 , ** client_args
290
427
)
291
428
receiver .open ()
292
429
receivers .append (receiver )
@@ -306,7 +443,7 @@ def auth_credentials_async(live_eventhub):
306
443
307
444
308
445
@pytest .fixture ()
309
- def auth_credential_receivers (live_eventhub , uamqp_transport ):
446
+ def auth_credential_receivers (live_eventhub , uamqp_transport , client_args ):
310
447
fully_qualified_namespace = live_eventhub ["hostname" ]
311
448
eventhub_name = live_eventhub ["event_hub" ]
312
449
partitions = [str (i ) for i in range (PARTITION_COUNT )]
@@ -325,7 +462,7 @@ def auth_credential_receivers(live_eventhub, uamqp_transport):
325
462
# TODO: TokenAuth should be fine?
326
463
sas_auth = SASTokenAuth (uri , uri , live_eventhub ["key_name" ], live_eventhub ["access_key" ])
327
464
receiver = ReceiveClient (
328
- live_eventhub ["hostname" ], source , auth = sas_auth , network_trace = False , timeout = 30 , link_credit = 500
465
+ live_eventhub ["hostname" ], source , auth = sas_auth , network_trace = False , timeout = 30 , link_credit = 500 , ** client_args
329
466
)
330
467
receiver .open ()
331
468
receivers .append (receiver )
@@ -335,7 +472,7 @@ def auth_credential_receivers(live_eventhub, uamqp_transport):
335
472
336
473
337
474
@pytest .fixture ()
338
- def auth_credential_receivers_async (live_eventhub , uamqp_transport ):
475
+ def auth_credential_receivers_async (live_eventhub , uamqp_transport , client_args ):
339
476
fully_qualified_namespace = live_eventhub ["hostname" ]
340
477
eventhub_name = live_eventhub ["event_hub" ]
341
478
partitions = [str (i ) for i in range (PARTITION_COUNT )]
@@ -354,7 +491,7 @@ def auth_credential_receivers_async(live_eventhub, uamqp_transport):
354
491
# TODO: TokenAuth should be fine?
355
492
sas_auth = SASTokenAuth (uri , uri , live_eventhub ["key_name" ], live_eventhub ["access_key" ])
356
493
receiver = ReceiveClient (
357
- live_eventhub ["hostname" ], source , auth = sas_auth , network_trace = False , timeout = 30 , link_credit = 500
494
+ live_eventhub ["hostname" ], source , auth = sas_auth , network_trace = False , timeout = 30 , link_credit = 500 , ** client_args
358
495
)
359
496
receiver .open ()
360
497
receivers .append (receiver )
@@ -364,14 +501,15 @@ def auth_credential_receivers_async(live_eventhub, uamqp_transport):
364
501
365
502
366
503
@pytest .fixture ()
367
- def auth_credential_senders (live_eventhub , uamqp_transport ):
504
+ def auth_credential_senders (live_eventhub , uamqp_transport , client_args ):
368
505
fully_qualified_namespace = live_eventhub ["hostname" ]
369
506
eventhub_name = live_eventhub ["event_hub" ]
370
507
client = EventHubProducerClient (
371
508
fully_qualified_namespace = fully_qualified_namespace ,
372
509
eventhub_name = eventhub_name ,
373
510
credential = get_devtools_credential (),
374
511
uamqp_transport = uamqp_transport ,
512
+ ** client_args ,
375
513
)
376
514
partitions = client .get_partition_ids ()
377
515
@@ -386,14 +524,15 @@ def auth_credential_senders(live_eventhub, uamqp_transport):
386
524
387
525
388
526
@pytest .fixture ()
389
- def auth_credential_senders_async (live_eventhub , uamqp_transport ):
527
+ def auth_credential_senders_async (live_eventhub , uamqp_transport , client_args ):
390
528
fully_qualified_namespace = live_eventhub ["hostname" ]
391
529
eventhub_name = live_eventhub ["event_hub" ]
392
530
client = EventHubProducerClient (
393
531
fully_qualified_namespace = fully_qualified_namespace ,
394
532
eventhub_name = eventhub_name ,
395
533
credential = get_devtools_credential (),
396
534
uamqp_transport = uamqp_transport ,
535
+ ** client_args ,
397
536
)
398
537
partitions = client .get_partition_ids ()
399
538
@@ -412,3 +551,4 @@ def auth_credential_senders_async(live_eventhub, uamqp_transport):
412
551
def pytest_configure (config ):
413
552
# register an additional marker
414
553
config .addinivalue_line ("markers" , "liveTest: mark test to be a live test only" )
554
+ config .addinivalue_line ("markers" , "no_amqpproxy: mark test to opt out of amqp proxy recording" )
0 commit comments