3131import time
3232import traceback
3333import platform
34+ from pathlib import Path
3435from threading import Event
3536from subprocess import call
3637from itertools import groupby
7273if not os .path .exists (path ):
7374 os .mkdir (path )
7475
76+ _failed_reports = []
77+ _preserved_ccm_log_scopes = set ()
78+ _log_artifact_dir = os .environ .get ('INTEGRATION_LOG_ARTIFACT_DIR' , 'integration-test-logs' )
79+ _preserved_file_extensions = ('.conf' , '.log' , '.yaml' , '.yml' )
80+
7581cass_version = None
7682cql_version = None
7783
7884
85+ def record_failed_test_report (report ):
86+ if report .failed :
87+ _failed_reports .append (report )
88+
89+
90+ def _forget_preserved_ccm_log_scope (cluster_name = None ):
91+ _preserved_ccm_log_scopes .discard (cluster_name )
92+ _preserved_ccm_log_scopes .discard (None )
93+
94+
95+ def preserve_ccm_logs_on_failure (cluster_name = None ):
96+ if not _failed_reports :
97+ return
98+
99+ ccm_root = Path (path )
100+ if cluster_name :
101+ ccm_root = ccm_root / cluster_name
102+ if not ccm_root .is_dir ():
103+ return
104+
105+ destination = Path (_log_artifact_dir ).resolve ()
106+ ccm_destination = destination / 'ccm'
107+ if cluster_name :
108+ ccm_destination = ccm_destination / cluster_name
109+ ccm_destination .mkdir (parents = True , exist_ok = True )
110+
111+ with (destination / 'failed-tests.txt' ).open ('w' ) as failed_tests :
112+ for report in _failed_reports :
113+ failed_tests .write ('%s %s\n ' % (report .when , report .nodeid ))
114+
115+ if (cluster_name in _preserved_ccm_log_scopes or
116+ (cluster_name and None in _preserved_ccm_log_scopes )):
117+ log .debug ("CCM logs already preserved for %s" , cluster_name or ccm_root )
118+ return
119+
120+ # Preserve CCM logs/configs before cleanup removes the cluster directories.
121+ for source_file in ccm_root .rglob ('*' ):
122+ if not source_file .is_file ():
123+ continue
124+
125+ rel_file = source_file .relative_to (ccm_root )
126+ preserve_all = bool ({'conf' , 'logs' }.intersection (rel_file .parts ))
127+ if not preserve_all and not source_file .name .endswith (_preserved_file_extensions ):
128+ continue
129+
130+ destination_file = ccm_destination / rel_file
131+ try :
132+ destination_file .parent .mkdir (parents = True , exist_ok = True )
133+ shutil .copy2 (source_file , destination_file )
134+ except OSError as exc :
135+ log .warning ("Failed to preserve file %s: %s" , source_file , exc )
136+
137+ _preserved_ccm_log_scopes .add (cluster_name )
138+ log .info ("Preserved ccm logs for failed tests under %s" , destination )
139+
140+
79141def get_server_versions ():
80142 """
81143 Probe system.local table to determine Cassandra and CQL version.
@@ -383,7 +445,10 @@ def remove_cluster():
383445 tries = 0
384446 while tries < 100 :
385447 try :
448+ cluster_name = CCM_CLUSTER .name
449+ preserve_ccm_logs_on_failure (CCM_CLUSTER .name )
386450 CCM_CLUSTER .remove ()
451+ _forget_preserved_ccm_log_scope (cluster_name )
387452 CCM_CLUSTER = None
388453 return
389454 except OSError :
@@ -455,7 +520,9 @@ def use_cluster(cluster_name, nodes, ipformat=None, start=True, workloads=None,
455520 try :
456521 CCM_CLUSTER = CCMClusterFactory .load (path , cluster_name )
457522 log .debug ("Found existing CCM cluster, {0}; clearing." .format (cluster_name ))
523+ preserve_ccm_logs_on_failure (cluster_name )
458524 CCM_CLUSTER .clear ()
525+ _forget_preserved_ccm_log_scope (cluster_name )
459526 CCM_CLUSTER .set_install_dir (** ccm_options )
460527 CCM_CLUSTER .set_configuration_options (configuration_options )
461528 CCM_CLUSTER .set_dse_configuration_options (dse_options )
@@ -471,7 +538,9 @@ def use_cluster(cluster_name, nodes, ipformat=None, start=True, workloads=None,
471538 # Make sure we cleanup old cluster dir if it exists
472539 cluster_path = os .path .join (path , cluster_name )
473540 if os .path .exists (cluster_path ):
541+ preserve_ccm_logs_on_failure (cluster_name )
474542 shutil .rmtree (cluster_path )
543+ _forget_preserved_ccm_log_scope (cluster_name )
475544
476545 if SCYLLA_VERSION :
477546 # `experimental: True` enable all experimental features.
@@ -553,6 +622,7 @@ def teardown_package():
553622 return
554623 # when multiple modules are run explicitly, this runs between them
555624 # need to make sure CCM_CLUSTER is properly cleared for that case
625+ preserve_ccm_logs_on_failure ()
556626 remove_cluster ()
557627 for cluster_name in [CLUSTER_NAME , MULTIDC_CLUSTER_NAME ]:
558628 try :
0 commit comments