99log = logging .getLogger ('archive_verify.workers' )
1010
1111
12- class PdcClient () :
12+ class PdcClient :
1313 """
1414 Base class representing a PDC client.
1515 Staging and production environments should instantiate PdcClient (default).
@@ -19,13 +19,15 @@ def __init__(self, archive_name, archive_pdc_path, archive_pdc_description, job_
1919 """
2020 :param archive_name: The name of the archive we shall download
2121 :param archive_pdc_path: The path in PDC TSM to the archive that we want to download
22- :param archive_pdc_description: The unique description that was used when uploading the archive to PDC
22+ :param archive_pdc_description: The unique description that was used when uploading the
23+ archive to PDC
2324 :param job_id: The current rq worker job id
2425 :param config: A dict containing the apps configuration
2526 """
2627 self .dest_root = config ["verify_root_dir" ]
2728 self .dsmc_log_dir = config ["dsmc_log_dir" ]
2829 self .whitelisted_warnings = config ["whitelisted_warnings" ]
30+ self .dsmc_extra_args = config .get ("dsmc_extra_args" , {})
2931 self .archive_name = archive_name
3032 self .archive_pdc_path = archive_pdc_path
3133 self .archive_pdc_description = archive_pdc_description
@@ -35,27 +37,50 @@ def dest(self):
3537 """
3638 :returns The unique path where the archive will be downloaded.
3739 """
38- return "{}_{}" .format (os .path .join (self .dest_root , self .archive_name ), self .job_id )
40+ return f"{ os .path .join (self .dest_root , self .archive_name )} _{ self .job_id } "
41+
42+ def dsmc_args (self ):
43+ """
44+ Fetch a list of arguments that will be passed to the dsmc command line. If there are
45+ extra arguments specified in the config, with "dsmc_extra_args", these are included as well.
46+ If arguments specified in dsmc_extra_args has the same key as the default arguments, the
47+ defaults will be overridden.
48+
49+ :return: a string with arguments that should be appended to the dsmc command line
50+ """
51+ key_values = {
52+ "subdir" : "yes" ,
53+ "description" : self .archive_pdc_description
54+ }
55+ key_values .update (self .dsmc_extra_args )
56+ args = [f"-{ k } ='{ v } '" for k , v in key_values .items () if v is not None ]
57+ args .extend ([f"-{ k } " for k , v in key_values .items () if v is None ])
58+ return " " .join (args )
3959
4060 def download (self ):
4161 """
4262 Downloads the specified archive from PDC to a unique location.
4363 :returns True if no errors or only whitelisted warnings were encountered, False otherwise
4464 """
45- log .info ("Download_from_pdc started for {}" .format (self .archive_pdc_path ))
46- cmd = "export DSM_LOG={} && dsmc retr {}/ {}/ -subdir=yes -description='{}'" .format (self .dsmc_log_dir ,
47- self .archive_pdc_path ,
48- self .dest (),
49- self .archive_pdc_description )
50- p = subprocess .Popen (cmd , shell = True , stdout = subprocess .PIPE , stderr = subprocess .STDOUT )
65+ log .info (f"Download_from_pdc started for { self .archive_pdc_path } " )
66+ cmd = f"export DSM_LOG={ self .dsmc_log_dir } && " \
67+ f"dsmc retr { self .archive_pdc_path } / { self .dest ()} / { self .dsmc_args ()} "
68+
69+ p = subprocess .Popen (
70+ cmd ,
71+ shell = True ,
72+ stdout = subprocess .PIPE ,
73+ stderr = subprocess .STDOUT ,
74+ text = True )
5175
5276 dsmc_output , _ = p .communicate ()
5377 dsmc_exit_code = p .returncode
5478
5579 if dsmc_exit_code != 0 :
56- return PdcClient ._parse_dsmc_return_code (dsmc_exit_code , dsmc_output , self .whitelisted_warnings )
80+ return PdcClient ._parse_dsmc_return_code (
81+ dsmc_exit_code , dsmc_output , self .whitelisted_warnings )
5782
58- log .info ("Download_from_pdc completed successfully for {}" . format ( self .archive_pdc_path ) )
83+ log .info (f "Download_from_pdc completed successfully for { self .archive_pdc_path } " )
5984 return True
6085
6186 def downloaded_archive_path (self ):
@@ -67,46 +92,42 @@ def cleanup(self):
6792 @staticmethod
6893 def _parse_dsmc_return_code (exit_code , output , whitelist ):
6994 """
70- Parses the dsmc output when we've encountered a non-zero exit code. For some certain exit codes,
71- warnings and errors we still want to return successfully.
95+ Parses the dsmc output when we've encountered a non-zero exit code. For some certain exit
96+ codes, warnings and errors we still want to return successfully.
7297
7398 :param exit_code: The exit code received from the failing dsmc process
7499 :param output: The text output from the dsmc process
75100 :param whitelist: A list of whitelisted warnings
76101 :returns True if only whitelisted warnings was encountered in the output, otherwise False
77102 """
78- log .info ("DSMC process returned an error!" )
79103
80104 # DSMC sets return code to 8 when a warning was encountered.
81- if exit_code == 8 :
82- log . info ( "DSMC process actually returned a warning. " )
105+ log_fn = log . warning if exit_code == 8 else log . error
106+ log_fn ( f "DSMC process returned a{ ' warning' if exit_code == 8 else 'n error' } ! " )
83107
84- output = output .splitlines ()
108+ # parse the DSMC output and extract error/warning codes and messages
109+ codes = []
110+ for line in output .splitlines ():
111+ if line .startswith ("ANS" ):
112+ log_fn (line )
85113
86- # Search through the DSMC log and see if we only have
87- # whitelisted warnings. If that is the case, change the
88- # return code to 0 instead. Otherwise keep the error state.
89- warnings = []
114+ matches = re .findall (r'ANS[0-9]+[EW]' , line )
115+ for match in matches :
116+ codes .append (match )
90117
91- for line in output :
92- matches = re .findall (r'ANS[0-9]+W' , line )
118+ unique_codes = set (sorted (codes ))
119+ if unique_codes :
120+ log_fn (f"ANS codes found in DSMC output: { ', ' .join (unique_codes )} " )
93121
94- for match in matches :
95- warnings .append (match )
122+ # if we only have whitelisted warnings, change the return code to 0 instead
123+ if unique_codes .issubset (set (whitelist )):
124+ log .info ("Only whitelisted DSMC ANS code(s) were encountered. Everything is OK." )
125+ return True
96126
97- log .info ("Warnings found in DSMC output: {}" .format (set (warnings )))
98-
99- for warning in warnings :
100- if warning not in whitelist :
101- log .error ("A non-whitelisted DSMC warning was encountered. Reporting it as an error! ('{}')" .format (
102- warning ))
103- return False
104-
105- log .info ("Only whitelisted DSMC warnings were encountered. Everything is OK." )
106- return True
107- else :
108- log .error ("An uncaught DSMC error code was encountered!" )
109- return False
127+ log .error (
128+ f"Non-whitelisted DSMC ANS code(s) encountered: "
129+ f"{ ', ' .join (unique_codes .difference (set (whitelist )))} " )
130+ return False
110131
111132
112133class MockPdcClient (PdcClient ):
@@ -138,8 +159,10 @@ def dest(self):
138159
139160 def download (self ):
140161 if not self .predownloaded_archive_path :
141- log .error (f"No archive containing the name { self .archive_name } found in { self .dest_root } " )
162+ log .error (
163+ f"No archive containing the name { self .archive_name } found in { self .dest_root } " )
142164 return False
143165 else :
144- log .info (f"Found pre-downloaded archive at { self .predownloaded_archive_path } " )
166+ log .info (
167+ f"Found pre-downloaded archive at { self .predownloaded_archive_path } " )
145168 return True
0 commit comments