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