1#!/usr/bin/env python 2 3r""" 4See class prolog below for details. 5""" 6 7import os 8import sys 9import yaml 10import time 11import platform 12from errno import EACCES, EPERM 13from ssh_utility import SSHRemoteclient 14 15 16class FFDCCollector: 17 18 r""" 19 Sends commands from configuration file to the targeted system to collect log files. 20 Fetch and store generated files at the specified location. 21 22 """ 23 24 def __init__(self, hostname, username, password, ffdc_config, location): 25 26 r""" 27 Description of argument(s): 28 29 hostname name/ip of the targeted (remote) system 30 username user on the targeted system with access to FFDC files 31 password password for user on targeted system 32 ffdc_config configuration file listing commands and files for FFDC 33 location Where to store collected FFDC 34 35 """ 36 if self.verify_script_env(): 37 self.hostname = hostname 38 self.username = username 39 self.password = password 40 self.ffdc_config = ffdc_config 41 self.location = location 42 self.remote_client = None 43 self.ffdc_dir_path = "" 44 self.ffdc_prefix = "" 45 self.receive_file_list = [] 46 self.target_type = "" 47 else: 48 sys.exit(-1) 49 50 def verify_script_env(self): 51 52 # Import to log version 53 import click 54 import paramiko 55 56 run_env_ok = True 57 print("\n\t---- Script host environment ----") 58 print("\t{:<10} {:<10}".format('Script hostname', os.uname()[1])) 59 print("\t{:<10} {:<10}".format('Script host os', platform.platform())) 60 print("\t{:<10} {:>10}".format('Python', platform.python_version())) 61 print("\t{:<10} {:>10}".format('PyYAML', yaml.__version__)) 62 print("\t{:<10} {:>10}".format('click', click.__version__)) 63 print("\t{:<10} {:>10}".format('paramiko', paramiko.__version__)) 64 65 if eval(yaml.__version__.replace('.', ',')) < (5, 4, 1): 66 print("\n\tERROR: Python or python packages do not meet minimum version requirement.") 67 print("\tERROR: PyYAML version 5.4.1 or higher is needed.\n") 68 run_env_ok = False 69 70 print("\t---- End script host environment ----") 71 return run_env_ok 72 73 def target_is_pingable(self): 74 75 r""" 76 Check if target system is ping-able. 77 78 """ 79 response = os.system("ping -c 1 -w 2 %s 2>&1 >/dev/null" % self.hostname) 80 if response == 0: 81 print("\n\t%s is ping-able." % self.hostname) 82 return True 83 else: 84 print("\n>>>>>\tERROR: %s is not ping-able. FFDC collection aborted.\n" % self.hostname) 85 sys.exit(-1) 86 87 def set_target_machine_type(self): 88 r""" 89 Determine and set target machine type. 90 91 """ 92 # Default to openbmc for first few sprints 93 self.target_type = "OPENBMC" 94 95 def collect_ffdc(self): 96 97 r""" 98 Initiate FFDC Collection depending on requested protocol. 99 100 """ 101 102 print("\n\n\t---- Start communicating with %s ----\n" % self.hostname) 103 if self.target_is_pingable(): 104 # Verify top level directory exists for storage 105 self.validate_local_store(self.location) 106 self.set_target_machine_type() 107 self.generate_ffdc() 108 109 def ssh_to_target_system(self): 110 r""" 111 Open a ssh connection to targeted system. 112 113 """ 114 115 self.remoteclient = SSHRemoteclient(self.hostname, 116 self.username, 117 self.password) 118 119 self.remoteclient.ssh_remoteclient_login() 120 121 def generate_ffdc(self): 122 123 r""" 124 Send commands in ffdc_config file to targeted system. 125 126 """ 127 128 with open(self.ffdc_config, 'r') as file: 129 ffdc_actions = yaml.load(file, Loader=yaml.FullLoader) 130 131 for machine_type in ffdc_actions.keys(): 132 if machine_type == self.target_type: 133 134 if (ffdc_actions[machine_type]['PROTOCOL'][0] == 'SSH'): 135 136 # Use SSH 137 self.ssh_to_target_system() 138 139 print("\n\tCollecting FFDC on " + self.hostname) 140 list_of_commands = ffdc_actions[machine_type]['COMMANDS'] 141 progress_counter = 0 142 for command in list_of_commands: 143 self.remoteclient.execute_command(command) 144 progress_counter += 1 145 self.print_progress(progress_counter) 146 147 print("\n\n\tCopying FFDC from remote system %s \n\n" % self.hostname) 148 # Get default values for scp action. 149 # self.location == local system for now 150 self.set_ffdc_defaults() 151 # Retrieving files from target system 152 list_of_files = ffdc_actions[machine_type]['FILES'] 153 self.scp_ffdc(self.ffdc_dir_path, self.ffdc_prefix, list_of_files) 154 else: 155 print("\n\tProtocol %s is not yet supported by this script.\n" 156 % ffdc_actions[machine_type]['PROTOCOL'][0]) 157 158 def scp_ffdc(self, 159 targ_dir_path, 160 targ_file_prefix="", 161 file_list=None, 162 quiet=None): 163 164 r""" 165 SCP all files in file_dict to the indicated directory on the local system. 166 167 Description of argument(s): 168 targ_dir_path The path of the directory to receive the files. 169 targ_file_prefix Prefix which will be pre-pended to each 170 target file's name. 171 file_dict A dictionary of files to scp from targeted system to this system 172 173 """ 174 175 self.remoteclient.scp_connection() 176 177 self.receive_file_list = [] 178 progress_counter = 0 179 for filename in file_list: 180 source_file_path = filename 181 targ_file_path = targ_dir_path + targ_file_prefix + filename.split('/')[-1] 182 183 # self.remoteclient.scp_file_from_remote() completed without exception, 184 # add file to the receiving file list. 185 scp_result = self.remoteclient.scp_file_from_remote(source_file_path, targ_file_path) 186 if scp_result: 187 self.receive_file_list.append(targ_file_path) 188 189 if not quiet: 190 if scp_result: 191 print("\t\tSuccessfully copied from " + self.hostname + ':' + source_file_path + ".\n") 192 else: 193 print("\t\tFail to copy from " + self.hostname + ':' + source_file_path + ".\n") 194 else: 195 progress_counter += 1 196 self.print_progress(progress_counter) 197 198 self.remoteclient.ssh_remoteclient_disconnect() 199 200 def set_ffdc_defaults(self): 201 202 r""" 203 Set a default value for self.ffdc_dir_path and self.ffdc_prefix. 204 Collected ffdc file will be stored in dir /self.location/hostname_timestr/. 205 Individual ffdc file will have timestr_filename. 206 207 Description of class variables: 208 self.ffdc_dir_path The dir path where collected ffdc data files should be put. 209 210 self.ffdc_prefix The prefix to be given to each ffdc file name. 211 212 """ 213 214 timestr = time.strftime("%Y%m%d-%H%M%S") 215 self.ffdc_dir_path = self.location + "/" + self.hostname + "_" + timestr + "/" 216 self.ffdc_prefix = timestr + "_" 217 self.validate_local_store(self.ffdc_dir_path) 218 219 def validate_local_store(self, dir_path): 220 r""" 221 Ensure path exists to store FFDC files locally. 222 223 Description of variable: 224 dir_path The dir path where collected ffdc data files will be stored. 225 226 """ 227 228 if not os.path.exists(dir_path): 229 try: 230 os.mkdir(dir_path, 0o755) 231 except (IOError, OSError) as e: 232 # PermissionError 233 if e.errno == EPERM or e.errno == EACCES: 234 print('>>>>>\tERROR: os.mkdir %s failed with PermissionError.\n' % dir_path) 235 else: 236 print('>>>>>\tERROR: os.mkdir %s failed with %s.\n' % (dir_path, e.strerror)) 237 sys.exit(-1) 238 239 def print_progress(self, progress): 240 r""" 241 Print activity progress + 242 243 Description of variable: 244 progress Progress counter. 245 246 """ 247 248 sys.stdout.write("\r\t" + "+" * progress) 249 sys.stdout.flush() 250 time.sleep(.1) 251