blob: 6eac17fbf86726fc74cd0b2af571fbcb4cde8b08 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Peter D Phan72ce6b82021-06-03 06:18:26 -05002
3r"""
4See class prolog below for details.
5"""
6
Patrick Williams20f38712022-12-08 06:18:26 -06007import json
8import logging
9import os
10import platform
11import re
12import subprocess
13import sys
14import time
George Keishing09679892022-12-08 08:21:52 -060015from errno import EACCES, EPERM
16
George Keishinge635ddc2022-12-08 07:38:02 -060017import yaml
Peter D Phan5e56f522021-12-20 13:19:41 -060018
George Keishing168545d2025-05-12 19:26:54 +053019sys.dont_write_bytecode = True
20
21
Peter D Phancb791d72022-02-08 12:23:03 -060022script_dir = os.path.dirname(os.path.abspath(__file__))
23sys.path.append(script_dir)
24# Walk path and append to sys.path
25for root, dirs, files in os.walk(script_dir):
26 for dir in dirs:
27 sys.path.append(os.path.join(root, dir))
28
Patrick Williams20f38712022-12-08 06:18:26 -060029from ssh_utility import SSHRemoteclient # NOQA
30from telnet_utility import TelnetRemoteclient # NOQA
Peter D Phan72ce6b82021-06-03 06:18:26 -050031
George Keishingb97a9042021-07-29 07:41:20 -050032r"""
33User define plugins python functions.
34
35It will imports files from directory plugins
36
37plugins
38├── file1.py
39└── file2.py
40
41Example how to define in YAML:
42 - plugin:
43 - plugin_name: plugin.foo_func.foo_func_yaml
44 - plugin_args:
45 - arg1
46 - arg2
47"""
George Keishing0e9b5ba2025-05-08 12:17:58 +053048plugin_dir = os.path.join(os.path.dirname(__file__), "plugins")
Peter D Phan5e56f522021-12-20 13:19:41 -060049sys.path.append(plugin_dir)
George Keishing0e9b5ba2025-05-08 12:17:58 +053050
51for module in os.listdir(plugin_dir):
52 if module == "__init__.py" or not module.endswith(".py"):
53 continue
54
55 plugin_module = f"plugins.{module[:-3]}"
56 try:
57 plugin = __import__(plugin_module, globals(), locals(), [], 0)
58 except Exception as e:
59 print(f"PLUGIN: Exception: {e}")
60 print(f"PLUGIN: Module import failed: {module}")
61 continue
George Keishingb97a9042021-07-29 07:41:20 -050062
63r"""
64This is for plugin functions returning data or responses to the caller
65in YAML plugin setup.
66
67Example:
68
69 - plugin:
70 - plugin_name: version = plugin.ssh_execution.ssh_execute_cmd
71 - plugin_args:
72 - ${hostname}
73 - ${username}
74 - ${password}
75 - "cat /etc/os-release | grep VERSION_ID | awk -F'=' '{print $2}'"
76 - plugin:
77 - plugin_name: plugin.print_vars.print_vars
78 - plugin_args:
79 - version
80
81where first plugin "version" var is used by another plugin in the YAML
82block or plugin
83
84"""
85global global_log_store_path
86global global_plugin_dict
87global global_plugin_list
George Keishing9348b402021-08-13 12:22:35 -050088
George Keishing0581cb02021-08-05 15:08:58 -050089# Hold the plugin return values in dict and plugin return vars in list.
George Keishing9348b402021-08-13 12:22:35 -050090# Dict is to reference and update vars processing in parser where as
91# list is for current vars from the plugin block which needs processing.
George Keishingb97a9042021-07-29 07:41:20 -050092global_plugin_dict = {}
93global_plugin_list = []
George Keishing9348b402021-08-13 12:22:35 -050094
George Keishingc754b432025-04-24 14:27:14 +053095# Hold the plugin return named declared if function returned values are
96# list,dict.
George Keishing0581cb02021-08-05 15:08:58 -050097# Refer this name list to look up the plugin dict for eval() args function
George Keishing9348b402021-08-13 12:22:35 -050098# Example ['version']
George Keishing0581cb02021-08-05 15:08:58 -050099global_plugin_type_list = []
George Keishing9348b402021-08-13 12:22:35 -0500100
101# Path where logs are to be stored or written.
Patrick Williams20f38712022-12-08 06:18:26 -0600102global_log_store_path = ""
George Keishingb97a9042021-07-29 07:41:20 -0500103
George Keishing1e7b0182021-08-06 14:05:54 -0500104# Plugin error state defaults.
105plugin_error_dict = {
Patrick Williams20f38712022-12-08 06:18:26 -0600106 "exit_on_error": False,
107 "continue_on_error": False,
George Keishing1e7b0182021-08-06 14:05:54 -0500108}
109
Peter D Phan72ce6b82021-06-03 06:18:26 -0500110
Peter D Phan5e56f522021-12-20 13:19:41 -0600111class ffdc_collector:
Peter D Phan72ce6b82021-06-03 06:18:26 -0500112 r"""
George Keishing1e7b0182021-08-06 14:05:54 -0500113 Execute commands from configuration file to collect log files.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500114 Fetch and store generated files at the specified location.
115
116 """
117
Patrick Williams20f38712022-12-08 06:18:26 -0600118 def __init__(
119 self,
120 hostname,
121 username,
122 password,
George Keishing7a61aa22023-06-26 13:18:37 +0530123 port_ssh,
George Keishinge8a41752023-06-22 21:42:47 +0530124 port_https,
125 port_ipmi,
Patrick Williams20f38712022-12-08 06:18:26 -0600126 ffdc_config,
127 location,
128 remote_type,
129 remote_protocol,
130 env_vars,
131 econfig,
132 log_level,
133 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500134 r"""
George Keishing04eb44e2025-05-16 22:14:14 +0530135 Initialize the FFDCCollector object with the provided parameters.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500136
George Keishing04eb44e2025-05-16 22:14:14 +0530137 This method initializes an FFDCCollector object with the given
138 attributes. The attributes represent the configuration for connecting
139 to a remote system, collecting log data, and storing the collected
140 data.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500141
George Keishing04eb44e2025-05-16 22:14:14 +0530142 Parameters:
143 hostname (str): Name or IP address of the targeted
144 (remote) system.
145 username (str): User on the targeted system with access
146 to log files.
147 password (str): Password for the user on the targeted
148 system.
149 port_ssh (int, optional): SSH port value. Defaults to 22.
150 port_https (int, optional): HTTPS port value. Defaults to 443.
151 port_ipmi (int, optional): IPMI port value. Defaults to 623.
152 ffdc_config (str): Configuration file listing commands
153 and files for FFDC.
154 location (str): Where to store collected log data.
155 remote_type (str): Block YAML type name of the remote
156 host.
157 remote_protocol (str): Protocol to use to collect data.
158 env_vars (dict, optional): User-defined CLI environment variables.
159 Defaults to None.
160 econfig (str, optional): User-defined environment variables
161 YAML file. Defaults to None.
162 log_level (str, optional): Log level for the collector.
163 Defaults to "INFO".
Peter D Phan72ce6b82021-06-03 06:18:26 -0500164 """
Peter D Phane86d9a52021-07-15 10:42:25 -0500165
166 self.hostname = hostname
167 self.username = username
168 self.password = password
George Keishing7a61aa22023-06-26 13:18:37 +0530169 self.port_ssh = str(port_ssh)
George Keishinge8a41752023-06-22 21:42:47 +0530170 self.port_https = str(port_https)
171 self.port_ipmi = str(port_ipmi)
Peter D Phane86d9a52021-07-15 10:42:25 -0500172 self.ffdc_config = ffdc_config
173 self.location = location + "/" + remote_type.upper()
174 self.ssh_remoteclient = None
175 self.telnet_remoteclient = None
176 self.ffdc_dir_path = ""
177 self.ffdc_prefix = ""
178 self.target_type = remote_type.upper()
179 self.remote_protocol = remote_protocol.upper()
George Keishing04eb44e2025-05-16 22:14:14 +0530180 self.env_vars = env_vars if env_vars else {}
181 self.econfig = econfig if econfig else {}
Peter D Phane86d9a52021-07-15 10:42:25 -0500182 self.start_time = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600183 self.elapsed_time = ""
George Keishing04eb44e2025-05-16 22:14:14 +0530184 self.env_dict = {}
Peter D Phane86d9a52021-07-15 10:42:25 -0500185 self.logger = None
186
George Keishing04eb44e2025-05-16 22:14:14 +0530187 """
188 Set prefix values for SCP files and directories.
189 Since the time stamp is at second granularity, these values are set
190 here to be sure that all files for this run will have the same
191 timestamps and be saved in the same directory.
192 self.location == local system for now
193 """
Peter D Phan5e56f522021-12-20 13:19:41 -0600194 self.set_ffdc_default_store_path()
Peter D Phane86d9a52021-07-15 10:42:25 -0500195
Peter D Phan5e56f522021-12-20 13:19:41 -0600196 # Logger for this run. Need to be after set_ffdc_default_store_path()
Peter D Phane86d9a52021-07-15 10:42:25 -0500197 self.script_logging(getattr(logging, log_level.upper()))
198
199 # Verify top level directory exists for storage
200 self.validate_local_store(self.location)
201
Peter D Phan72ce6b82021-06-03 06:18:26 -0500202 if self.verify_script_env():
George Keishing04eb44e2025-05-16 22:14:14 +0530203 try:
204 with open(self.ffdc_config, "r") as file:
205 self.ffdc_actions = yaml.safe_load(file)
206 except yaml.YAMLError as e:
207 self.logger.error(e)
208 sys.exit(-1)
Peter D Phane86d9a52021-07-15 10:42:25 -0500209
George Keishing04eb44e2025-05-16 22:14:14 +0530210 if self.target_type not in self.ffdc_actions:
Peter D Phane86d9a52021-07-15 10:42:25 -0500211 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600212 "\n\tERROR: %s is not listed in %s.\n\n"
213 % (self.target_type, self.ffdc_config)
214 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500215 sys.exit(-1)
George Keishing04eb44e2025-05-16 22:14:14 +0530216
217 self.logger.info("\n\tENV: User define input YAML variables")
218 self.env_dict = self.load_env()
Peter D Phan72ce6b82021-06-03 06:18:26 -0500219 else:
Peter D Phan8462faf2021-06-16 12:24:15 -0500220 sys.exit(-1)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500221
222 def verify_script_env(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500223 # Import to log version
224 import click
225 import paramiko
226
227 run_env_ok = True
Peter D Phan0c669772021-06-24 13:52:42 -0500228
George Keishingd805bc02025-02-28 12:17:13 +0530229 try:
230 redfishtool_version = (
231 self.run_tool_cmd("redfishtool -V").split(" ")[2].strip("\n")
232 )
233 except Exception as e:
234 self.logger.error("\tEXCEPTION redfishtool: %s", e)
235 redfishtool_version = "Not Installed (optional)"
236
237 try:
238 ipmitool_version = self.run_tool_cmd("ipmitool -V").split(" ")[2]
239 except Exception as e:
240 self.logger.error("\tEXCEPTION ipmitool: %s", e)
241 ipmitool_version = "Not Installed (optional)"
Peter D Phan0c669772021-06-24 13:52:42 -0500242
Peter D Phane86d9a52021-07-15 10:42:25 -0500243 self.logger.info("\n\t---- Script host environment ----")
Patrick Williams20f38712022-12-08 06:18:26 -0600244 self.logger.info(
245 "\t{:<10} {:<10}".format("Script hostname", os.uname()[1])
246 )
247 self.logger.info(
248 "\t{:<10} {:<10}".format("Script host os", platform.platform())
249 )
250 self.logger.info(
251 "\t{:<10} {:>10}".format("Python", platform.python_version())
252 )
253 self.logger.info("\t{:<10} {:>10}".format("PyYAML", yaml.__version__))
254 self.logger.info("\t{:<10} {:>10}".format("click", click.__version__))
255 self.logger.info(
256 "\t{:<10} {:>10}".format("paramiko", paramiko.__version__)
257 )
258 self.logger.info(
259 "\t{:<10} {:>9}".format("redfishtool", redfishtool_version)
260 )
261 self.logger.info(
262 "\t{:<10} {:>12}".format("ipmitool", ipmitool_version)
263 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500264
Patrick Williams20f38712022-12-08 06:18:26 -0600265 if eval(yaml.__version__.replace(".", ",")) < (5, 3, 0):
266 self.logger.error(
267 "\n\tERROR: Python or python packages do not meet minimum"
268 " version requirement."
269 )
270 self.logger.error(
271 "\tERROR: PyYAML version 5.3.0 or higher is needed.\n"
272 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500273 run_env_ok = False
274
Peter D Phane86d9a52021-07-15 10:42:25 -0500275 self.logger.info("\t---- End script host environment ----")
Peter D Phan72ce6b82021-06-03 06:18:26 -0500276 return run_env_ok
277
Patrick Williams20f38712022-12-08 06:18:26 -0600278 def script_logging(self, log_level_attr):
Peter D Phane86d9a52021-07-15 10:42:25 -0500279 r"""
280 Create logger
281
282 """
283 self.logger = logging.getLogger()
284 self.logger.setLevel(log_level_attr)
Patrick Williams20f38712022-12-08 06:18:26 -0600285 log_file_handler = logging.FileHandler(
286 self.ffdc_dir_path + "collector.log"
287 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500288
289 stdout_handler = logging.StreamHandler(sys.stdout)
290 self.logger.addHandler(log_file_handler)
291 self.logger.addHandler(stdout_handler)
292
293 # Turn off paramiko INFO logging
294 logging.getLogger("paramiko").setLevel(logging.WARNING)
295
Peter D Phan72ce6b82021-06-03 06:18:26 -0500296 def target_is_pingable(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500297 r"""
298 Check if target system is ping-able.
299
300 """
George Keishing0662e942021-07-13 05:12:20 -0500301 response = os.system("ping -c 1 %s 2>&1 >/dev/null" % self.hostname)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500302 if response == 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600303 self.logger.info(
304 "\n\t[Check] %s is ping-able.\t\t [OK]" % self.hostname
305 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500306 return True
307 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500308 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600309 "\n\tERROR: %s is not ping-able. FFDC collection aborted.\n"
310 % self.hostname
311 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500312 sys.exit(-1)
313
Peter D Phan72ce6b82021-06-03 06:18:26 -0500314 def collect_ffdc(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500315 r"""
316 Initiate FFDC Collection depending on requested protocol.
317
318 """
319
Patrick Williams20f38712022-12-08 06:18:26 -0600320 self.logger.info(
321 "\n\t---- Start communicating with %s ----" % self.hostname
322 )
Peter D Phan7610bc42021-07-06 06:31:05 -0500323 self.start_time = time.time()
Peter D Phan0c669772021-06-24 13:52:42 -0500324
George Keishingf5a57502021-07-22 16:43:47 -0500325 # Find the list of target and protocol supported.
326 check_protocol_list = []
327 config_dict = self.ffdc_actions
Peter D Phan0c669772021-06-24 13:52:42 -0500328
George Keishingf5a57502021-07-22 16:43:47 -0500329 for target_type in config_dict.keys():
330 if self.target_type != target_type:
331 continue
George Keishingeafba182021-06-29 13:44:58 -0500332
George Keishingf5a57502021-07-22 16:43:47 -0500333 for k, v in config_dict[target_type].items():
Patrick Williams20f38712022-12-08 06:18:26 -0600334 if (
335 config_dict[target_type][k]["PROTOCOL"][0]
336 not in check_protocol_list
337 ):
338 check_protocol_list.append(
339 config_dict[target_type][k]["PROTOCOL"][0]
340 )
Peter D Phanbff617a2021-07-22 08:41:35 -0500341
Patrick Williams20f38712022-12-08 06:18:26 -0600342 self.logger.info(
343 "\n\t %s protocol type: %s"
344 % (self.target_type, check_protocol_list)
345 )
Peter D Phanbff617a2021-07-22 08:41:35 -0500346
George Keishingf5a57502021-07-22 16:43:47 -0500347 verified_working_protocol = self.verify_protocol(check_protocol_list)
Peter D Phanbff617a2021-07-22 08:41:35 -0500348
George Keishingf5a57502021-07-22 16:43:47 -0500349 if verified_working_protocol:
Patrick Williams20f38712022-12-08 06:18:26 -0600350 self.logger.info(
351 "\n\t---- Completed protocol pre-requisite check ----\n"
352 )
Peter D Phan0c669772021-06-24 13:52:42 -0500353
George Keishingf5a57502021-07-22 16:43:47 -0500354 # Verify top level directory exists for storage
355 self.validate_local_store(self.location)
356
Patrick Williams20f38712022-12-08 06:18:26 -0600357 if (self.remote_protocol not in verified_working_protocol) and (
358 self.remote_protocol != "ALL"
359 ):
360 self.logger.info(
361 "\n\tWorking protocol list: %s" % verified_working_protocol
362 )
George Keishingf5a57502021-07-22 16:43:47 -0500363 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600364 "\tERROR: Requested protocol %s is not in working protocol"
George Keishing7899a452023-02-15 02:46:54 -0600365 " list.\n" % self.remote_protocol
Patrick Williams20f38712022-12-08 06:18:26 -0600366 )
George Keishingf5a57502021-07-22 16:43:47 -0500367 sys.exit(-1)
368 else:
369 self.generate_ffdc(verified_working_protocol)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500370
371 def ssh_to_target_system(self):
372 r"""
373 Open a ssh connection to targeted system.
374
375 """
376
Patrick Williams20f38712022-12-08 06:18:26 -0600377 self.ssh_remoteclient = SSHRemoteclient(
George Keishing7a61aa22023-06-26 13:18:37 +0530378 self.hostname, self.username, self.password, self.port_ssh
Patrick Williams20f38712022-12-08 06:18:26 -0600379 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500380
Peter D Phan5963d632021-07-12 09:58:55 -0500381 if self.ssh_remoteclient.ssh_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600382 self.logger.info(
383 "\n\t[Check] %s SSH connection established.\t [OK]"
384 % self.hostname
385 )
Peter D Phan733df632021-06-17 13:13:36 -0500386
Peter D Phan5963d632021-07-12 09:58:55 -0500387 # Check scp connection.
388 # If scp connection fails,
389 # continue with FFDC generation but skip scp files to local host.
390 self.ssh_remoteclient.scp_connection()
391 return True
392 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600393 self.logger.info(
394 "\n\t[Check] %s SSH connection.\t [NOT AVAILABLE]"
395 % self.hostname
396 )
Peter D Phan5963d632021-07-12 09:58:55 -0500397 return False
398
399 def telnet_to_target_system(self):
400 r"""
401 Open a telnet connection to targeted system.
402 """
Patrick Williams20f38712022-12-08 06:18:26 -0600403 self.telnet_remoteclient = TelnetRemoteclient(
404 self.hostname, self.username, self.password
405 )
Peter D Phan5963d632021-07-12 09:58:55 -0500406 if self.telnet_remoteclient.tn_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600407 self.logger.info(
408 "\n\t[Check] %s Telnet connection established.\t [OK]"
409 % self.hostname
410 )
Peter D Phan5963d632021-07-12 09:58:55 -0500411 return True
412 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600413 self.logger.info(
414 "\n\t[Check] %s Telnet connection.\t [NOT AVAILABLE]"
415 % self.hostname
416 )
Peter D Phan5963d632021-07-12 09:58:55 -0500417 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500418
George Keishing772c9772021-06-16 23:23:42 -0500419 def generate_ffdc(self, working_protocol_list):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500420 r"""
Peter D Phan04aca3b2021-06-21 10:37:18 -0500421 Determine actions based on remote host type
Peter D Phan72ce6b82021-06-03 06:18:26 -0500422
Peter D Phan04aca3b2021-06-21 10:37:18 -0500423 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530424 working_protocol_list List of confirmed working protocols to
425 connect to remote host.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500426 """
427
Patrick Williams20f38712022-12-08 06:18:26 -0600428 self.logger.info(
429 "\n\t---- Executing commands on " + self.hostname + " ----"
430 )
431 self.logger.info(
432 "\n\tWorking protocol list: %s" % working_protocol_list
433 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500434
George Keishingf5a57502021-07-22 16:43:47 -0500435 config_dict = self.ffdc_actions
436 for target_type in config_dict.keys():
437 if self.target_type != target_type:
George Keishing6ea92b02021-07-01 11:20:50 -0500438 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500439
Peter D Phane86d9a52021-07-15 10:42:25 -0500440 self.logger.info("\n\tFFDC Path: %s " % self.ffdc_dir_path)
Patrick Williams20f38712022-12-08 06:18:26 -0600441 global_plugin_dict["global_log_store_path"] = self.ffdc_dir_path
George Keishingf5a57502021-07-22 16:43:47 -0500442 self.logger.info("\tSystem Type: %s" % target_type)
443 for k, v in config_dict[target_type].items():
Patrick Williams20f38712022-12-08 06:18:26 -0600444 if (
445 self.remote_protocol not in working_protocol_list
446 and self.remote_protocol != "ALL"
447 ):
George Keishing6ea92b02021-07-01 11:20:50 -0500448 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500449
Patrick Williams20f38712022-12-08 06:18:26 -0600450 protocol = config_dict[target_type][k]["PROTOCOL"][0]
George Keishingf5a57502021-07-22 16:43:47 -0500451
452 if protocol not in working_protocol_list:
453 continue
454
George Keishingb7607612021-07-27 13:31:23 -0500455 if protocol in working_protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -0600456 if protocol == "SSH" or protocol == "SCP":
George Keishing12fd0652021-07-27 13:57:11 -0500457 self.protocol_ssh(protocol, target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600458 elif protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -0500459 self.protocol_telnet(target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600460 elif (
461 protocol == "REDFISH"
462 or protocol == "IPMI"
463 or protocol == "SHELL"
464 ):
George Keishing506b0582021-07-27 09:31:22 -0500465 self.protocol_execute(protocol, target_type, k)
George Keishingb7607612021-07-27 13:31:23 -0500466 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600467 self.logger.error(
468 "\n\tERROR: %s is not available for %s."
469 % (protocol, self.hostname)
470 )
George Keishingeafba182021-06-29 13:44:58 -0500471
Peter D Phan04aca3b2021-06-21 10:37:18 -0500472 # Close network connection after collecting all files
Patrick Williams20f38712022-12-08 06:18:26 -0600473 self.elapsed_time = time.strftime(
474 "%H:%M:%S", time.gmtime(time.time() - self.start_time)
475 )
George Keishing48972ba2025-05-05 17:40:29 +0530476 self.logger.info("\n\tTotal time taken: %s" % self.elapsed_time)
Peter D Phanbff617a2021-07-22 08:41:35 -0500477 if self.ssh_remoteclient:
478 self.ssh_remoteclient.ssh_remoteclient_disconnect()
479 if self.telnet_remoteclient:
480 self.telnet_remoteclient.tn_remoteclient_disconnect()
Peter D Phan04aca3b2021-06-21 10:37:18 -0500481
Patrick Williams20f38712022-12-08 06:18:26 -0600482 def protocol_ssh(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500483 r"""
484 Perform actions using SSH and SCP protocols.
485
486 Description of argument(s):
George Keishing12fd0652021-07-27 13:57:11 -0500487 protocol Protocol to execute.
George Keishingf5a57502021-07-22 16:43:47 -0500488 target_type OS Type of remote host.
George Keishing6ea92b02021-07-01 11:20:50 -0500489 sub_type Group type of commands.
Peter D Phan0c669772021-06-24 13:52:42 -0500490 """
491
Patrick Williams20f38712022-12-08 06:18:26 -0600492 if protocol == "SCP":
George Keishingf5a57502021-07-22 16:43:47 -0500493 self.group_copy(self.ffdc_actions[target_type][sub_type])
George Keishing6ea92b02021-07-01 11:20:50 -0500494 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600495 self.collect_and_copy_ffdc(
496 self.ffdc_actions[target_type][sub_type]
497 )
Peter D Phan0c669772021-06-24 13:52:42 -0500498
Patrick Williams20f38712022-12-08 06:18:26 -0600499 def protocol_telnet(self, target_type, sub_type):
Peter D Phan5963d632021-07-12 09:58:55 -0500500 r"""
501 Perform actions using telnet protocol.
502 Description of argument(s):
George Keishingf5a57502021-07-22 16:43:47 -0500503 target_type OS Type of remote host.
Peter D Phan5963d632021-07-12 09:58:55 -0500504 """
Patrick Williams20f38712022-12-08 06:18:26 -0600505 self.logger.info(
506 "\n\t[Run] Executing commands on %s using %s"
507 % (self.hostname, "TELNET")
508 )
Peter D Phan5963d632021-07-12 09:58:55 -0500509 telnet_files_saved = []
510 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600511 list_of_commands = self.ffdc_actions[target_type][sub_type]["COMMANDS"]
Peter D Phan5963d632021-07-12 09:58:55 -0500512 for index, each_cmd in enumerate(list_of_commands, start=0):
513 command_txt, command_timeout = self.unpack_command(each_cmd)
Patrick Williams20f38712022-12-08 06:18:26 -0600514 result = self.telnet_remoteclient.execute_command(
515 command_txt, command_timeout
516 )
Peter D Phan5963d632021-07-12 09:58:55 -0500517 if result:
518 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600519 targ_file = self.ffdc_actions[target_type][sub_type][
520 "FILES"
521 ][index]
Peter D Phan5963d632021-07-12 09:58:55 -0500522 except IndexError:
Peter D Phane86d9a52021-07-15 10:42:25 -0500523 targ_file = command_txt
524 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600525 "\n\t[WARN] Missing filename to store data from"
526 " telnet %s." % each_cmd
527 )
528 self.logger.warning(
529 "\t[WARN] Data will be stored in %s." % targ_file
530 )
531 targ_file_with_path = (
532 self.ffdc_dir_path + self.ffdc_prefix + targ_file
533 )
Peter D Phan5963d632021-07-12 09:58:55 -0500534 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600535 with open(targ_file_with_path, "w") as fp:
Peter D Phan5963d632021-07-12 09:58:55 -0500536 fp.write(result)
537 fp.close
538 telnet_files_saved.append(targ_file)
539 progress_counter += 1
540 self.print_progress(progress_counter)
Peter D Phane86d9a52021-07-15 10:42:25 -0500541 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan5963d632021-07-12 09:58:55 -0500542 for file in telnet_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500543 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
Peter D Phan5963d632021-07-12 09:58:55 -0500544
Patrick Williams20f38712022-12-08 06:18:26 -0600545 def protocol_execute(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500546 r"""
George Keishing506b0582021-07-27 09:31:22 -0500547 Perform actions for a given protocol.
Peter D Phan0c669772021-06-24 13:52:42 -0500548
549 Description of argument(s):
George Keishing506b0582021-07-27 09:31:22 -0500550 protocol Protocol to execute.
George Keishingf5a57502021-07-22 16:43:47 -0500551 target_type OS Type of remote host.
George Keishing6ea92b02021-07-01 11:20:50 -0500552 sub_type Group type of commands.
Peter D Phan0c669772021-06-24 13:52:42 -0500553 """
554
Patrick Williams20f38712022-12-08 06:18:26 -0600555 self.logger.info(
556 "\n\t[Run] Executing commands to %s using %s"
557 % (self.hostname, protocol)
558 )
George Keishing506b0582021-07-27 09:31:22 -0500559 executed_files_saved = []
George Keishingeafba182021-06-29 13:44:58 -0500560 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600561 list_of_cmd = self.get_command_list(
562 self.ffdc_actions[target_type][sub_type]
563 )
George Keishingeafba182021-06-29 13:44:58 -0500564 for index, each_cmd in enumerate(list_of_cmd, start=0):
George Keishingcaa97e62021-08-03 14:00:09 -0500565 plugin_call = False
George Keishingb97a9042021-07-29 07:41:20 -0500566 if isinstance(each_cmd, dict):
Patrick Williams20f38712022-12-08 06:18:26 -0600567 if "plugin" in each_cmd:
George Keishing1e7b0182021-08-06 14:05:54 -0500568 # If the error is set and plugin explicitly
569 # requested to skip execution on error..
Patrick Williams20f38712022-12-08 06:18:26 -0600570 if plugin_error_dict[
571 "exit_on_error"
572 ] and self.plugin_error_check(each_cmd["plugin"]):
573 self.logger.info(
574 "\n\t[PLUGIN-ERROR] exit_on_error: %s"
575 % plugin_error_dict["exit_on_error"]
576 )
577 self.logger.info(
578 "\t[PLUGIN-SKIP] %s" % each_cmd["plugin"][0]
579 )
George Keishing1e7b0182021-08-06 14:05:54 -0500580 continue
George Keishingcaa97e62021-08-03 14:00:09 -0500581 plugin_call = True
George Keishingb97a9042021-07-29 07:41:20 -0500582 # call the plugin
583 self.logger.info("\n\t[PLUGIN-START]")
Patrick Williams20f38712022-12-08 06:18:26 -0600584 result = self.execute_plugin_block(each_cmd["plugin"])
George Keishingb97a9042021-07-29 07:41:20 -0500585 self.logger.info("\t[PLUGIN-END]\n")
George Keishingb97a9042021-07-29 07:41:20 -0500586 else:
George Keishing2b83e042021-08-03 12:56:11 -0500587 each_cmd = self.yaml_env_and_plugin_vars_populate(each_cmd)
George Keishingb97a9042021-07-29 07:41:20 -0500588
George Keishingcaa97e62021-08-03 14:00:09 -0500589 if not plugin_call:
590 result = self.run_tool_cmd(each_cmd)
George Keishingeafba182021-06-29 13:44:58 -0500591 if result:
592 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600593 file_name = self.get_file_list(
594 self.ffdc_actions[target_type][sub_type]
595 )[index]
George Keishingb97a9042021-07-29 07:41:20 -0500596 # If file is specified as None.
George Keishing0581cb02021-08-05 15:08:58 -0500597 if file_name == "None":
George Keishingb97a9042021-07-29 07:41:20 -0500598 continue
Patrick Williams20f38712022-12-08 06:18:26 -0600599 targ_file = self.yaml_env_and_plugin_vars_populate(
600 file_name
601 )
George Keishingeafba182021-06-29 13:44:58 -0500602 except IndexError:
Patrick Williams20f38712022-12-08 06:18:26 -0600603 targ_file = each_cmd.split("/")[-1]
George Keishing506b0582021-07-27 09:31:22 -0500604 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600605 "\n\t[WARN] Missing filename to store data from %s."
606 % each_cmd
607 )
608 self.logger.warning(
609 "\t[WARN] Data will be stored in %s." % targ_file
610 )
George Keishingeafba182021-06-29 13:44:58 -0500611
Patrick Williams20f38712022-12-08 06:18:26 -0600612 targ_file_with_path = (
613 self.ffdc_dir_path + self.ffdc_prefix + targ_file
614 )
George Keishingeafba182021-06-29 13:44:58 -0500615
616 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600617 with open(targ_file_with_path, "w") as fp:
George Keishing91308ea2021-08-10 14:43:15 -0500618 if isinstance(result, dict):
619 fp.write(json.dumps(result))
620 else:
621 fp.write(result)
George Keishingeafba182021-06-29 13:44:58 -0500622 fp.close
George Keishing506b0582021-07-27 09:31:22 -0500623 executed_files_saved.append(targ_file)
George Keishingeafba182021-06-29 13:44:58 -0500624
625 progress_counter += 1
626 self.print_progress(progress_counter)
627
Peter D Phane86d9a52021-07-15 10:42:25 -0500628 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
George Keishingeafba182021-06-29 13:44:58 -0500629
George Keishing506b0582021-07-27 09:31:22 -0500630 for file in executed_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500631 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
George Keishingeafba182021-06-29 13:44:58 -0500632
Patrick Williams20f38712022-12-08 06:18:26 -0600633 def collect_and_copy_ffdc(
634 self, ffdc_actions_for_target_type, form_filename=False
635 ):
Peter D Phan04aca3b2021-06-21 10:37:18 -0500636 r"""
637 Send commands in ffdc_config file to targeted system.
638
639 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530640 ffdc_actions_for_target_type Commands and files for the selected
641 remote host type.
642 form_filename If true, pre-pend self.target_type to
643 filename
Peter D Phan04aca3b2021-06-21 10:37:18 -0500644 """
645
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500646 # Executing commands, if any
Patrick Williams20f38712022-12-08 06:18:26 -0600647 self.ssh_execute_ffdc_commands(
648 ffdc_actions_for_target_type, form_filename
649 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500650
Peter D Phan3beb02e2021-07-06 13:25:17 -0500651 # Copying files
Peter D Phan5963d632021-07-12 09:58:55 -0500652 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600653 self.logger.info(
654 "\n\n\tCopying FFDC files from remote system %s.\n"
655 % self.hostname
656 )
Peter D Phan2b8052d2021-06-22 10:55:41 -0500657
Peter D Phan04aca3b2021-06-21 10:37:18 -0500658 # Retrieving files from target system
George Keishingf5a57502021-07-22 16:43:47 -0500659 list_of_files = self.get_file_list(ffdc_actions_for_target_type)
Patrick Williams20f38712022-12-08 06:18:26 -0600660 self.scp_ffdc(
661 self.ffdc_dir_path,
662 self.ffdc_prefix,
663 form_filename,
664 list_of_files,
665 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500666 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600667 self.logger.info(
668 "\n\n\tSkip copying FFDC files from remote system %s.\n"
669 % self.hostname
670 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500671
Patrick Williams20f38712022-12-08 06:18:26 -0600672 def get_command_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500673 r"""
674 Fetch list of commands from configuration file
675
676 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530677 ffdc_actions_for_target_type Commands and files for the selected
678 remote host type.
Peter D Phanbabf2962021-07-07 11:24:40 -0500679 """
680 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600681 list_of_commands = ffdc_actions_for_target_type["COMMANDS"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500682 except KeyError:
683 list_of_commands = []
684 return list_of_commands
685
Patrick Williams20f38712022-12-08 06:18:26 -0600686 def get_file_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500687 r"""
688 Fetch list of commands from configuration file
689
690 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530691 ffdc_actions_for_target_type Commands and files for the selected
692 remote host type.
Peter D Phanbabf2962021-07-07 11:24:40 -0500693 """
694 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600695 list_of_files = ffdc_actions_for_target_type["FILES"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500696 except KeyError:
697 list_of_files = []
698 return list_of_files
699
Patrick Williams20f38712022-12-08 06:18:26 -0600700 def unpack_command(self, command):
Peter D Phan5963d632021-07-12 09:58:55 -0500701 r"""
702 Unpack command from config file
703
704 Description of argument(s):
705 command Command from config file.
706 """
707 if isinstance(command, dict):
708 command_txt = next(iter(command))
709 command_timeout = next(iter(command.values()))
710 elif isinstance(command, str):
711 command_txt = command
712 # Default command timeout 60 seconds
713 command_timeout = 60
714
715 return command_txt, command_timeout
716
Patrick Williams20f38712022-12-08 06:18:26 -0600717 def ssh_execute_ffdc_commands(
718 self, ffdc_actions_for_target_type, form_filename=False
719 ):
Peter D Phan3beb02e2021-07-06 13:25:17 -0500720 r"""
721 Send commands in ffdc_config file to targeted system.
722
723 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530724 ffdc_actions_for_target_type Commands and files for the selected
725 remote host type.
726 form_filename If true, pre-pend self.target_type to
727 filename
Peter D Phan3beb02e2021-07-06 13:25:17 -0500728 """
Patrick Williams20f38712022-12-08 06:18:26 -0600729 self.logger.info(
730 "\n\t[Run] Executing commands on %s using %s"
731 % (self.hostname, ffdc_actions_for_target_type["PROTOCOL"][0])
732 )
Peter D Phan3beb02e2021-07-06 13:25:17 -0500733
George Keishingf5a57502021-07-22 16:43:47 -0500734 list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500735 # If command list is empty, returns
736 if not list_of_commands:
737 return
738
739 progress_counter = 0
740 for command in list_of_commands:
Peter D Phan5963d632021-07-12 09:58:55 -0500741 command_txt, command_timeout = self.unpack_command(command)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500742
743 if form_filename:
744 command_txt = str(command_txt % self.target_type)
745
Patrick Williams20f38712022-12-08 06:18:26 -0600746 (
747 cmd_exit_code,
748 err,
749 response,
750 ) = self.ssh_remoteclient.execute_command(
751 command_txt, command_timeout
752 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500753
754 if cmd_exit_code:
755 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600756 "\n\t\t[WARN] %s exits with code %s."
757 % (command_txt, str(cmd_exit_code))
758 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500759 self.logger.warning("\t\t[WARN] %s " % err)
Peter D Phanbabf2962021-07-07 11:24:40 -0500760
Peter D Phan3beb02e2021-07-06 13:25:17 -0500761 progress_counter += 1
762 self.print_progress(progress_counter)
763
Peter D Phane86d9a52021-07-15 10:42:25 -0500764 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan3beb02e2021-07-06 13:25:17 -0500765
Patrick Williams20f38712022-12-08 06:18:26 -0600766 def group_copy(self, ffdc_actions_for_target_type):
Peter D Phan56429a62021-06-23 08:38:29 -0500767 r"""
768 scp group of files (wild card) from remote host.
769
770 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530771 fdc_actions_for_target_type Commands and files for the selected
772 remote host type.
Peter D Phan56429a62021-06-23 08:38:29 -0500773 """
Peter D Phan3beb02e2021-07-06 13:25:17 -0500774
Peter D Phan5963d632021-07-12 09:58:55 -0500775 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600776 self.logger.info(
777 "\n\tCopying files from remote system %s via SCP.\n"
778 % self.hostname
779 )
Peter D Phan56429a62021-06-23 08:38:29 -0500780
Patrick Williams20f38712022-12-08 06:18:26 -0600781 list_of_commands = self.get_command_list(
782 ffdc_actions_for_target_type
783 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500784 # If command list is empty, returns
785 if not list_of_commands:
786 return
Peter D Phan56429a62021-06-23 08:38:29 -0500787
Peter D Phanbabf2962021-07-07 11:24:40 -0500788 for command in list_of_commands:
789 try:
George Keishingb4540e72021-08-02 13:48:46 -0500790 command = self.yaml_env_and_plugin_vars_populate(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500791 except IndexError:
George Keishingb4540e72021-08-02 13:48:46 -0500792 self.logger.error("\t\tInvalid command %s" % command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500793 continue
794
Patrick Williams20f38712022-12-08 06:18:26 -0600795 (
796 cmd_exit_code,
797 err,
798 response,
799 ) = self.ssh_remoteclient.execute_command(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500800
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500801 # If file does not exist, code take no action.
802 # cmd_exit_code is ignored for this scenario.
Peter D Phan56429a62021-06-23 08:38:29 -0500803 if response:
Patrick Williams20f38712022-12-08 06:18:26 -0600804 scp_result = self.ssh_remoteclient.scp_file_from_remote(
805 response.split("\n"), self.ffdc_dir_path
806 )
Peter D Phan56429a62021-06-23 08:38:29 -0500807 if scp_result:
Patrick Williams20f38712022-12-08 06:18:26 -0600808 self.logger.info(
809 "\t\tSuccessfully copied from "
810 + self.hostname
811 + ":"
812 + command
813 )
Peter D Phan56429a62021-06-23 08:38:29 -0500814 else:
George Keishinga56e87b2021-08-06 00:24:19 -0500815 self.logger.info("\t\t%s has no result" % command)
Peter D Phan56429a62021-06-23 08:38:29 -0500816
817 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600818 self.logger.info(
819 "\n\n\tSkip copying files from remote system %s.\n"
820 % self.hostname
821 )
Peter D Phan56429a62021-06-23 08:38:29 -0500822
Patrick Williams20f38712022-12-08 06:18:26 -0600823 def scp_ffdc(
824 self,
825 targ_dir_path,
826 targ_file_prefix,
827 form_filename,
828 file_list=None,
829 quiet=None,
830 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500831 r"""
George Keishingc754b432025-04-24 14:27:14 +0530832 SCP all files in file_dict to the indicated directory on the local
833 system.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500834
835 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530836 targ_dir_path The path of the directory to receive
837 the files.
George Keishinge16f1582022-12-15 07:32:21 -0600838 targ_file_prefix Prefix which will be prepended to each
Peter D Phan72ce6b82021-06-03 06:18:26 -0500839 target file's name.
George Keishingc754b432025-04-24 14:27:14 +0530840 file_dict A dictionary of files to scp from
841 targeted system to this system
Peter D Phan72ce6b82021-06-03 06:18:26 -0500842
843 """
844
Peter D Phan72ce6b82021-06-03 06:18:26 -0500845 progress_counter = 0
846 for filename in file_list:
Peter D Phan2b8052d2021-06-22 10:55:41 -0500847 if form_filename:
848 filename = str(filename % self.target_type)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500849 source_file_path = filename
Patrick Williams20f38712022-12-08 06:18:26 -0600850 targ_file_path = (
851 targ_dir_path + targ_file_prefix + filename.split("/")[-1]
852 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500853
Peter D Phanbabf2962021-07-07 11:24:40 -0500854 # If source file name contains wild card, copy filename as is.
Patrick Williams20f38712022-12-08 06:18:26 -0600855 if "*" in source_file_path:
856 scp_result = self.ssh_remoteclient.scp_file_from_remote(
857 source_file_path, self.ffdc_dir_path
858 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500859 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600860 scp_result = self.ssh_remoteclient.scp_file_from_remote(
861 source_file_path, targ_file_path
862 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500863
864 if not quiet:
865 if scp_result:
Peter D Phane86d9a52021-07-15 10:42:25 -0500866 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -0600867 "\t\tSuccessfully copied from "
868 + self.hostname
869 + ":"
870 + source_file_path
871 + ".\n"
872 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500873 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500874 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -0600875 "\t\tFail to copy from "
876 + self.hostname
877 + ":"
878 + source_file_path
879 + ".\n"
880 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500881 else:
882 progress_counter += 1
883 self.print_progress(progress_counter)
884
Peter D Phan5e56f522021-12-20 13:19:41 -0600885 def set_ffdc_default_store_path(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500886 r"""
887 Set a default value for self.ffdc_dir_path and self.ffdc_prefix.
George Keishingc754b432025-04-24 14:27:14 +0530888 Collected ffdc file will be stored in dir
889 /self.location/hostname_timestr/.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500890 Individual ffdc file will have timestr_filename.
891
892 Description of class variables:
George Keishingc754b432025-04-24 14:27:14 +0530893 self.ffdc_dir_path The dir path where collected ffdc data files
894 should be put.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500895
896 self.ffdc_prefix The prefix to be given to each ffdc file name.
897
898 """
899
900 timestr = time.strftime("%Y%m%d-%H%M%S")
Patrick Williams20f38712022-12-08 06:18:26 -0600901 self.ffdc_dir_path = (
902 self.location + "/" + self.hostname + "_" + timestr + "/"
903 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500904 self.ffdc_prefix = timestr + "_"
905 self.validate_local_store(self.ffdc_dir_path)
906
Peter D Phan5e56f522021-12-20 13:19:41 -0600907 # Need to verify local store path exists prior to instantiate this class.
908 # This class method is used to share the same code between CLI input parm
909 # and Robot Framework "${EXECDIR}/logs" before referencing this class.
910 @classmethod
911 def validate_local_store(cls, dir_path):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500912 r"""
913 Ensure path exists to store FFDC files locally.
914
915 Description of variable:
916 dir_path The dir path where collected ffdc data files will be stored.
917
918 """
919
920 if not os.path.exists(dir_path):
921 try:
George Keishing7b3a5132021-07-13 09:24:02 -0500922 os.makedirs(dir_path, 0o755)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500923 except (IOError, OSError) as e:
924 # PermissionError
925 if e.errno == EPERM or e.errno == EACCES:
George Keishing15352052025-04-24 18:55:47 +0530926 print(
Patrick Williams20f38712022-12-08 06:18:26 -0600927 "\tERROR: os.makedirs %s failed with"
928 " PermissionError.\n" % dir_path
929 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500930 else:
George Keishing15352052025-04-24 18:55:47 +0530931 print(
Patrick Williams20f38712022-12-08 06:18:26 -0600932 "\tERROR: os.makedirs %s failed with %s.\n"
933 % (dir_path, e.strerror)
934 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500935 sys.exit(-1)
936
937 def print_progress(self, progress):
938 r"""
939 Print activity progress +
940
941 Description of variable:
942 progress Progress counter.
943
944 """
945
946 sys.stdout.write("\r\t" + "+" * progress)
947 sys.stdout.flush()
Patrick Williams20f38712022-12-08 06:18:26 -0600948 time.sleep(0.1)
Peter D Phan0c669772021-06-24 13:52:42 -0500949
950 def verify_redfish(self):
951 r"""
952 Verify remote host has redfish service active
953
954 """
Patrick Williams20f38712022-12-08 06:18:26 -0600955 redfish_parm = (
956 "redfishtool -r "
957 + self.hostname
George Keishing7a61aa22023-06-26 13:18:37 +0530958 + ":"
959 + self.port_https
Patrick Williams20f38712022-12-08 06:18:26 -0600960 + " -S Always raw GET /redfish/v1/"
961 )
962 return self.run_tool_cmd(redfish_parm, True)
Peter D Phan0c669772021-06-24 13:52:42 -0500963
George Keishingeafba182021-06-29 13:44:58 -0500964 def verify_ipmi(self):
965 r"""
966 Verify remote host has IPMI LAN service active
967
968 """
Patrick Williams20f38712022-12-08 06:18:26 -0600969 if self.target_type == "OPENBMC":
970 ipmi_parm = (
971 "ipmitool -I lanplus -C 17 -U "
972 + self.username
973 + " -P "
974 + self.password
975 + " -H "
976 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +0530977 + " -p "
978 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -0600979 + " power status"
980 )
George Keishing484f8242021-07-27 01:42:02 -0500981 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600982 ipmi_parm = (
983 "ipmitool -I lanplus -P "
984 + self.password
985 + " -H "
986 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +0530987 + " -p "
988 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -0600989 + " power status"
990 )
George Keishing484f8242021-07-27 01:42:02 -0500991
Patrick Williams20f38712022-12-08 06:18:26 -0600992 return self.run_tool_cmd(ipmi_parm, True)
George Keishingeafba182021-06-29 13:44:58 -0500993
Patrick Williams20f38712022-12-08 06:18:26 -0600994 def run_tool_cmd(self, parms_string, quiet=False):
George Keishingeafba182021-06-29 13:44:58 -0500995 r"""
George Keishing506b0582021-07-27 09:31:22 -0500996 Run CLI standard tool or scripts.
George Keishingeafba182021-06-29 13:44:58 -0500997
998 Description of variable:
George Keishing506b0582021-07-27 09:31:22 -0500999 parms_string tool command options.
1000 quiet do not print tool error message if True
George Keishingeafba182021-06-29 13:44:58 -05001001 """
1002
Patrick Williams20f38712022-12-08 06:18:26 -06001003 result = subprocess.run(
1004 [parms_string],
1005 stdout=subprocess.PIPE,
1006 stderr=subprocess.PIPE,
1007 shell=True,
1008 universal_newlines=True,
1009 )
George Keishingeafba182021-06-29 13:44:58 -05001010
1011 if result.stderr and not quiet:
George Keishing0e9b5ba2025-05-08 12:17:58 +05301012 if self.password in parms_string:
1013 parms_string = parms_string.replace(self.password, "********")
Patrick Williams20f38712022-12-08 06:18:26 -06001014 self.logger.error("\n\t\tERROR with %s " % parms_string)
1015 self.logger.error("\t\t" + result.stderr)
George Keishingeafba182021-06-29 13:44:58 -05001016
1017 return result.stdout
George Keishing04d29102021-07-16 02:05:57 -05001018
George Keishingf5a57502021-07-22 16:43:47 -05001019 def verify_protocol(self, protocol_list):
1020 r"""
1021 Perform protocol working check.
1022
1023 Description of argument(s):
1024 protocol_list List of protocol.
1025 """
1026
1027 tmp_list = []
1028 if self.target_is_pingable():
1029 tmp_list.append("SHELL")
1030
1031 for protocol in protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -06001032 if self.remote_protocol != "ALL":
George Keishingf5a57502021-07-22 16:43:47 -05001033 if self.remote_protocol != protocol:
1034 continue
1035
1036 # Only check SSH/SCP once for both protocols
Patrick Williams20f38712022-12-08 06:18:26 -06001037 if (
1038 protocol == "SSH"
1039 or protocol == "SCP"
1040 and protocol not in tmp_list
1041 ):
George Keishingf5a57502021-07-22 16:43:47 -05001042 if self.ssh_to_target_system():
George Keishingaa638702021-07-26 11:48:28 -05001043 # Add only what user asked.
Patrick Williams20f38712022-12-08 06:18:26 -06001044 if self.remote_protocol != "ALL":
George Keishingaa638702021-07-26 11:48:28 -05001045 tmp_list.append(self.remote_protocol)
1046 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001047 tmp_list.append("SSH")
1048 tmp_list.append("SCP")
George Keishingf5a57502021-07-22 16:43:47 -05001049
Patrick Williams20f38712022-12-08 06:18:26 -06001050 if protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -05001051 if self.telnet_to_target_system():
1052 tmp_list.append(protocol)
1053
Patrick Williams20f38712022-12-08 06:18:26 -06001054 if protocol == "REDFISH":
George Keishingf5a57502021-07-22 16:43:47 -05001055 if self.verify_redfish():
1056 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001057 self.logger.info(
1058 "\n\t[Check] %s Redfish Service.\t\t [OK]"
1059 % self.hostname
1060 )
George Keishingf5a57502021-07-22 16:43:47 -05001061 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001062 self.logger.info(
1063 "\n\t[Check] %s Redfish Service.\t\t [NOT AVAILABLE]"
1064 % self.hostname
1065 )
George Keishingf5a57502021-07-22 16:43:47 -05001066
Patrick Williams20f38712022-12-08 06:18:26 -06001067 if protocol == "IPMI":
George Keishingf5a57502021-07-22 16:43:47 -05001068 if self.verify_ipmi():
1069 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001070 self.logger.info(
1071 "\n\t[Check] %s IPMI LAN Service.\t\t [OK]"
1072 % self.hostname
1073 )
George Keishingf5a57502021-07-22 16:43:47 -05001074 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001075 self.logger.info(
1076 "\n\t[Check] %s IPMI LAN Service.\t\t [NOT AVAILABLE]"
1077 % self.hostname
1078 )
George Keishingf5a57502021-07-22 16:43:47 -05001079
1080 return tmp_list
George Keishinge1686752021-07-27 12:55:28 -05001081
1082 def load_env(self):
1083 r"""
George Keishing0e9b5ba2025-05-08 12:17:58 +05301084 Load the user environment variables from a YAML file.
George Keishinge1686752021-07-27 12:55:28 -05001085
George Keishing0e9b5ba2025-05-08 12:17:58 +05301086 This method reads the environment variables from a YAML file specified
1087 in the ENV_FILE environment variable. If the file is not found or
1088 there is an error reading the file, an exception is raised.
1089
1090 The YAML file should have the following format:
1091
1092 .. code-block:: yaml
1093
1094 VAR_NAME: VAR_VALUE
1095
1096 Where VAR_NAME is the name of the environment variable, and
1097 VAR_VALUE is its value.
1098
1099 After loading the environment variables, they are stored in the
1100 self.env attribute for later use.
George Keishinge1686752021-07-27 12:55:28 -05001101 """
George Keishing0e9b5ba2025-05-08 12:17:58 +05301102
Patrick Williams20f38712022-12-08 06:18:26 -06001103 os.environ["hostname"] = self.hostname
1104 os.environ["username"] = self.username
1105 os.environ["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301106 os.environ["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301107 os.environ["port_https"] = self.port_https
1108 os.environ["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001109
1110 # Append default Env.
Patrick Williams20f38712022-12-08 06:18:26 -06001111 self.env_dict["hostname"] = self.hostname
1112 self.env_dict["username"] = self.username
1113 self.env_dict["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301114 self.env_dict["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301115 self.env_dict["port_https"] = self.port_https
1116 self.env_dict["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001117
1118 try:
1119 tmp_env_dict = {}
1120 if self.env_vars:
1121 tmp_env_dict = json.loads(self.env_vars)
1122 # Export ENV vars default.
1123 for key, value in tmp_env_dict.items():
1124 os.environ[key] = value
1125 self.env_dict[key] = str(value)
1126
George Keishing0e9b5ba2025-05-08 12:17:58 +05301127 # Load user specified ENV config YAML.
George Keishinge1686752021-07-27 12:55:28 -05001128 if self.econfig:
Patrick Williams20f38712022-12-08 06:18:26 -06001129 with open(self.econfig, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -05001130 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -07001131 tmp_env_dict = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -05001132 except yaml.YAMLError as e:
1133 self.logger.error(e)
1134 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001135 # Export ENV vars.
Patrick Williams20f38712022-12-08 06:18:26 -06001136 for key, value in tmp_env_dict["env_params"].items():
George Keishinge1686752021-07-27 12:55:28 -05001137 os.environ[key] = str(value)
1138 self.env_dict[key] = str(value)
1139 except json.decoder.JSONDecodeError as e:
1140 self.logger.error("\n\tERROR: %s " % e)
1141 sys.exit(-1)
George Keishing0e9b5ba2025-05-08 12:17:58 +05301142 except FileNotFoundError as e:
1143 self.logger.error("\n\tERROR: %s " % e)
1144 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001145
1146 # This to mask the password from displaying on the console.
1147 mask_dict = self.env_dict.copy()
1148 for k, v in mask_dict.items():
1149 if k.lower().find("password") != -1:
1150 hidden_text = []
1151 hidden_text.append(v)
Patrick Williams20f38712022-12-08 06:18:26 -06001152 password_regex = (
1153 "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1154 )
George Keishinge1686752021-07-27 12:55:28 -05001155 mask_dict[k] = re.sub(password_regex, "********", v)
1156
1157 self.logger.info(json.dumps(mask_dict, indent=8, sort_keys=False))
George Keishingb97a9042021-07-29 07:41:20 -05001158
1159 def execute_python_eval(self, eval_string):
1160 r"""
George Keishing9348b402021-08-13 12:22:35 -05001161 Execute qualified python function string using eval.
George Keishingb97a9042021-07-29 07:41:20 -05001162
1163 Description of argument(s):
1164 eval_string Execute the python object.
1165
1166 Example:
1167 eval(plugin.foo_func.foo_func(10))
1168 """
1169 try:
George Keishingdda48ce2021-08-12 07:02:27 -05001170 self.logger.info("\tExecuting plugin func()")
1171 self.logger.debug("\tCall func: %s" % eval_string)
George Keishingb97a9042021-07-29 07:41:20 -05001172 result = eval(eval_string)
1173 self.logger.info("\treturn: %s" % str(result))
Patrick Williams20f38712022-12-08 06:18:26 -06001174 except (
1175 ValueError,
1176 SyntaxError,
1177 NameError,
1178 AttributeError,
1179 TypeError,
1180 ) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001181 self.logger.error("\tERROR: execute_python_eval: %s" % e)
1182 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001183 plugin_error_dict["exit_on_error"] = True
George Keishing73b95d12021-08-13 14:30:52 -05001184 self.logger.info("\treturn: PLUGIN_EVAL_ERROR")
Patrick Williams20f38712022-12-08 06:18:26 -06001185 return "PLUGIN_EVAL_ERROR"
George Keishingb97a9042021-07-29 07:41:20 -05001186
1187 return result
1188
1189 def execute_plugin_block(self, plugin_cmd_list):
1190 r"""
Peter D Phan5e56f522021-12-20 13:19:41 -06001191 Pack the plugin command to qualifed python string object.
George Keishingb97a9042021-07-29 07:41:20 -05001192
1193 Description of argument(s):
1194 plugin_list_dict Plugin block read from YAML
1195 [{'plugin_name': 'plugin.foo_func.my_func'},
1196 {'plugin_args': [10]}]
1197
1198 Example:
1199 - plugin:
1200 - plugin_name: plugin.foo_func.my_func
1201 - plugin_args:
1202 - arg1
1203 - arg2
1204
1205 - plugin:
1206 - plugin_name: result = plugin.foo_func.my_func
1207 - plugin_args:
1208 - arg1
1209 - arg2
1210
1211 - plugin:
1212 - plugin_name: result1,result2 = plugin.foo_func.my_func
1213 - plugin_args:
1214 - arg1
1215 - arg2
1216 """
1217 try:
Patrick Williams20f38712022-12-08 06:18:26 -06001218 idx = self.key_index_list_dict("plugin_name", plugin_cmd_list)
1219 plugin_name = plugin_cmd_list[idx]["plugin_name"]
George Keishingb97a9042021-07-29 07:41:20 -05001220 # Equal separator means plugin function returns result.
Patrick Williams20f38712022-12-08 06:18:26 -06001221 if " = " in plugin_name:
George Keishingb97a9042021-07-29 07:41:20 -05001222 # Ex. ['result', 'plugin.foo_func.my_func']
Patrick Williams20f38712022-12-08 06:18:26 -06001223 plugin_name_args = plugin_name.split(" = ")
George Keishingb97a9042021-07-29 07:41:20 -05001224 # plugin func return data.
1225 for arg in plugin_name_args:
1226 if arg == plugin_name_args[-1]:
1227 plugin_name = arg
1228 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001229 plugin_resp = arg.split(",")
George Keishingb97a9042021-07-29 07:41:20 -05001230 # ['result1','result2']
1231 for x in plugin_resp:
1232 global_plugin_list.append(x)
1233 global_plugin_dict[x] = ""
1234
1235 # Walk the plugin args ['arg1,'arg2']
1236 # If the YAML plugin statement 'plugin_args' is not declared.
George Keishingf0eb1d62025-05-14 15:07:02 +05301237 plugin_args = []
Patrick Williams20f38712022-12-08 06:18:26 -06001238 if any("plugin_args" in d for d in plugin_cmd_list):
1239 idx = self.key_index_list_dict("plugin_args", plugin_cmd_list)
George Keishingf0eb1d62025-05-14 15:07:02 +05301240 if idx is not None:
1241 plugin_args = plugin_cmd_list[idx].get("plugin_args", [])
George Keishingb97a9042021-07-29 07:41:20 -05001242 plugin_args = self.yaml_args_populate(plugin_args)
1243 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301244 plugin_args = self.yaml_args_populate([])
George Keishingb97a9042021-07-29 07:41:20 -05001245
George Keishing450f92f2025-05-15 23:12:51 +05301246 # Pack the args list to string parameters for plugin function.
George Keishingb97a9042021-07-29 07:41:20 -05001247 parm_args_str = self.yaml_args_string(plugin_args)
George Keishing450f92f2025-05-15 23:12:51 +05301248
1249 """
1250 Example of plugin_func:
1251 plugin.redfish.enumerate_request(
1252 "xx.xx.xx.xx:443",
1253 "root",
1254 "********",
1255 "/redfish/v1/",
1256 "json")
1257 """
George Keishingb97a9042021-07-29 07:41:20 -05001258 if parm_args_str:
George Keishing450f92f2025-05-15 23:12:51 +05301259 plugin_func = f"{plugin_name}({parm_args_str})"
George Keishingb97a9042021-07-29 07:41:20 -05001260 else:
George Keishing450f92f2025-05-15 23:12:51 +05301261 plugin_func = f"{plugin_name}()"
George Keishingb97a9042021-07-29 07:41:20 -05001262
1263 # Execute plugin function.
1264 if global_plugin_dict:
1265 resp = self.execute_python_eval(plugin_func)
George Keishing9348b402021-08-13 12:22:35 -05001266 # Update plugin vars dict if there is any.
Patrick Williams20f38712022-12-08 06:18:26 -06001267 if resp != "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001268 self.response_args_data(resp)
George Keishingb97a9042021-07-29 07:41:20 -05001269 else:
George Keishingcaa97e62021-08-03 14:00:09 -05001270 resp = self.execute_python_eval(plugin_func)
George Keishingb97a9042021-07-29 07:41:20 -05001271 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001272 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001273 plugin_error_dict["exit_on_error"] = True
George Keishing1e7b0182021-08-06 14:05:54 -05001274 self.logger.error("\tERROR: execute_plugin_block: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001275 pass
1276
George Keishing73b95d12021-08-13 14:30:52 -05001277 # There is a real error executing the plugin function.
Patrick Williams20f38712022-12-08 06:18:26 -06001278 if resp == "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001279 return resp
1280
George Keishingde79a9b2021-08-12 16:14:43 -05001281 # Check if plugin_expects_return (int, string, list,dict etc)
Patrick Williams20f38712022-12-08 06:18:26 -06001282 if any("plugin_expects_return" in d for d in plugin_cmd_list):
1283 idx = self.key_index_list_dict(
1284 "plugin_expects_return", plugin_cmd_list
1285 )
1286 plugin_expects = plugin_cmd_list[idx]["plugin_expects_return"]
George Keishingde79a9b2021-08-12 16:14:43 -05001287 if plugin_expects:
1288 if resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001289 if (
1290 self.plugin_expect_type(plugin_expects, resp)
1291 == "INVALID"
1292 ):
George Keishingde79a9b2021-08-12 16:14:43 -05001293 self.logger.error("\tWARN: Plugin error check skipped")
1294 elif not self.plugin_expect_type(plugin_expects, resp):
Patrick Williams20f38712022-12-08 06:18:26 -06001295 self.logger.error(
1296 "\tERROR: Plugin expects return data: %s"
1297 % plugin_expects
1298 )
1299 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001300 elif not resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001301 self.logger.error(
1302 "\tERROR: Plugin func failed to return data"
1303 )
1304 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001305
1306 return resp
1307
George Keishingb97a9042021-07-29 07:41:20 -05001308 def response_args_data(self, plugin_resp):
1309 r"""
George Keishing9348b402021-08-13 12:22:35 -05001310 Parse the plugin function response and update plugin return variable.
George Keishingb97a9042021-07-29 07:41:20 -05001311
1312 plugin_resp Response data from plugin function.
1313 """
1314 resp_list = []
George Keishing5765f792021-08-02 13:08:53 -05001315 resp_data = ""
George Keishing9348b402021-08-13 12:22:35 -05001316
George Keishingb97a9042021-07-29 07:41:20 -05001317 # There is nothing to update the plugin response.
Patrick Williams20f38712022-12-08 06:18:26 -06001318 if len(global_plugin_list) == 0 or plugin_resp == "None":
George Keishingb97a9042021-07-29 07:41:20 -05001319 return
1320
George Keishing5765f792021-08-02 13:08:53 -05001321 if isinstance(plugin_resp, str):
Patrick Williams20f38712022-12-08 06:18:26 -06001322 resp_data = plugin_resp.strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001323 resp_list.append(resp_data)
1324 elif isinstance(plugin_resp, bytes):
Patrick Williams20f38712022-12-08 06:18:26 -06001325 resp_data = str(plugin_resp, "UTF-8").strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001326 resp_list.append(resp_data)
1327 elif isinstance(plugin_resp, tuple):
1328 if len(global_plugin_list) == 1:
George Keishingb97a9042021-07-29 07:41:20 -05001329 resp_list.append(plugin_resp)
George Keishing5765f792021-08-02 13:08:53 -05001330 else:
1331 resp_list = list(plugin_resp)
Patrick Williams20f38712022-12-08 06:18:26 -06001332 resp_list = [x.strip("\r\n\t") for x in resp_list]
George Keishingb97a9042021-07-29 07:41:20 -05001333 elif isinstance(plugin_resp, list):
George Keishing5765f792021-08-02 13:08:53 -05001334 if len(global_plugin_list) == 1:
Patrick Williams20f38712022-12-08 06:18:26 -06001335 resp_list.append([x.strip("\r\n\t") for x in plugin_resp])
George Keishing5765f792021-08-02 13:08:53 -05001336 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001337 resp_list = [x.strip("\r\n\t") for x in plugin_resp]
George Keishing5765f792021-08-02 13:08:53 -05001338 elif isinstance(plugin_resp, int) or isinstance(plugin_resp, float):
1339 resp_list.append(plugin_resp)
George Keishingb97a9042021-07-29 07:41:20 -05001340
George Keishing9348b402021-08-13 12:22:35 -05001341 # Iterate if there is a list of plugin return vars to update.
George Keishingb97a9042021-07-29 07:41:20 -05001342 for idx, item in enumerate(resp_list, start=0):
George Keishing9348b402021-08-13 12:22:35 -05001343 # Exit loop, done required loop.
George Keishingb97a9042021-07-29 07:41:20 -05001344 if idx >= len(global_plugin_list):
1345 break
1346 # Find the index of the return func in the list and
1347 # update the global func return dictionary.
1348 try:
1349 dict_idx = global_plugin_list[idx]
1350 global_plugin_dict[dict_idx] = item
1351 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001352 self.logger.warn("\tWARN: response_args_data: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001353 pass
1354
1355 # Done updating plugin dict irrespective of pass or failed,
George Keishing9348b402021-08-13 12:22:35 -05001356 # clear all the list element for next plugin block execute.
George Keishingb97a9042021-07-29 07:41:20 -05001357 global_plugin_list.clear()
1358
1359 def yaml_args_string(self, plugin_args):
1360 r"""
George Keishing450f92f2025-05-15 23:12:51 +05301361 Pack the arguments into a string representation.
George Keishingb97a9042021-07-29 07:41:20 -05001362
George Keishing450f92f2025-05-15 23:12:51 +05301363 This method processes the plugin_arg argument, which is expected to
1364 contain a list of arguments. The method iterates through the list,
1365 converts each argument to a string, and concatenates them into a
1366 single string. Special handling is applied for integer, float, and
1367 predefined plugin variable types.
1368
1369 Ecample:
1370 From
1371 ['xx.xx.xx.xx:443', 'root', '********', '/redfish/v1/', 'json']
1372 to
1373 "xx.xx.xx.xx:443","root","********","/redfish/v1/","json"
1374
1375 Parameters:
1376 plugin_args (list): A list of arguments to be packed into
1377 a string.
1378
1379 Returns:
1380 str: A string representation of the arguments.
George Keishingb97a9042021-07-29 07:41:20 -05001381 """
Patrick Williams20f38712022-12-08 06:18:26 -06001382 args_str = ""
George Keishing450f92f2025-05-15 23:12:51 +05301383
1384 for i, arg in enumerate(plugin_args):
1385 if arg:
1386 if isinstance(arg, (int, float)):
1387 args_str += str(arg)
1388 elif arg in global_plugin_type_list:
1389 args_str += str(global_plugin_dict[arg])
George Keishingb97a9042021-07-29 07:41:20 -05001390 else:
George Keishing450f92f2025-05-15 23:12:51 +05301391 args_str += f'"{arg.strip("\r\n\t")}"'
1392
1393 # Skip last list element.
1394 if i != len(plugin_args) - 1:
1395 args_str += ","
1396
George Keishingb97a9042021-07-29 07:41:20 -05001397 return args_str
1398
1399 def yaml_args_populate(self, yaml_arg_list):
1400 r"""
George Keishingf0eb1d62025-05-14 15:07:02 +05301401 Decode environment and plugin variables and populate the argument list.
George Keishingb97a9042021-07-29 07:41:20 -05001402
George Keishingf0eb1d62025-05-14 15:07:02 +05301403 This method processes the yaml_arg_list argument, which is expected to
1404 contain a list of arguments read from a YAML file. The method iterates
1405 through the list, decodes environment and plugin variables, and
1406 returns a populated list of arguments.
George Keishingb97a9042021-07-29 07:41:20 -05001407
George Keishingf0eb1d62025-05-14 15:07:02 +05301408 .. code-block:: yaml
1409
George Keishingb97a9042021-07-29 07:41:20 -05001410 - plugin_args:
1411 - arg1
1412 - arg2
1413
George Keishingf0eb1d62025-05-14 15:07:02 +05301414 ['${hostname}:${port_https}', '${username}', '/redfish/v1/', 'json']
George Keishingb97a9042021-07-29 07:41:20 -05001415
George Keishingf0eb1d62025-05-14 15:07:02 +05301416 Returns the populated plugin list
1417 ['xx.xx.xx.xx:443', 'root', '/redfish/v1/', 'json']
1418
1419 Parameters:
1420 yaml_arg_list (list): A list of arguments containing environment
1421 and plugin variables.
1422
1423 Returns:
1424 list: A populated list of arguments with decoded environment and
1425 plugin variables.
1426 """
George Keishingb97a9042021-07-29 07:41:20 -05001427 if isinstance(yaml_arg_list, list):
George Keishingf0eb1d62025-05-14 15:07:02 +05301428 populated_list = []
George Keishingb97a9042021-07-29 07:41:20 -05001429 for arg in yaml_arg_list:
George Keishing0581cb02021-08-05 15:08:58 -05001430 if isinstance(arg, (int, float)):
George Keishingf0eb1d62025-05-14 15:07:02 +05301431 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001432 elif isinstance(arg, str):
1433 arg_str = self.yaml_env_and_plugin_vars_populate(str(arg))
George Keishingf0eb1d62025-05-14 15:07:02 +05301434 populated_list.append(arg_str)
George Keishingb97a9042021-07-29 07:41:20 -05001435 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301436 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001437
George Keishingf0eb1d62025-05-14 15:07:02 +05301438 return populated_list
George Keishingb97a9042021-07-29 07:41:20 -05001439
1440 def yaml_env_and_plugin_vars_populate(self, yaml_arg_str):
1441 r"""
George Keishinga593f4b2025-05-13 20:02:36 +05301442 Update environment variables and plugin variables based on the
1443 provided YAML argument string.
George Keishingb97a9042021-07-29 07:41:20 -05001444
George Keishinga593f4b2025-05-13 20:02:36 +05301445 This method processes the yaml_arg_str argument, which is expected
1446 to contain a string representing environment variables and plugin
1447 variables in the format:
George Keishingb97a9042021-07-29 07:41:20 -05001448
George Keishinga593f4b2025-05-13 20:02:36 +05301449 .. code-block:: yaml
1450
George Keishingb97a9042021-07-29 07:41:20 -05001451 - cat ${MY_VAR}
1452 - ls -AX my_plugin_var
George Keishinga593f4b2025-05-13 20:02:36 +05301453
1454 The method parses the string, extracts the variable names, and updates
1455 the corresponding environment variables and plugin variables.
1456
1457 Parameters:
1458 yaml_arg_str (str): A string containing environment and plugin
1459 variable definitions in YAML format.
1460
1461 Returns:
1462 str: The updated YAML argument string with plugin variables
1463 replaced.
George Keishingb97a9042021-07-29 07:41:20 -05001464 """
George Keishinga593f4b2025-05-13 20:02:36 +05301465
1466 # Parse and convert the Plugin YAML vars string to python vars
1467 # Example:
1468 # ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
George Keishingb97a9042021-07-29 07:41:20 -05001469 try:
George Keishingc754b432025-04-24 14:27:14 +05301470 # Example, list of matching
1471 # env vars ['username', 'password', 'hostname']
George Keishingb97a9042021-07-29 07:41:20 -05001472 # Extra escape \ for special symbols. '\$\{([^\}]+)\}' works good.
George Keishinga593f4b2025-05-13 20:02:36 +05301473 env_var_regex = r"\$\{([^\}]+)\}"
1474 env_var_names_list = re.findall(env_var_regex, yaml_arg_str)
1475
George Keishingb97a9042021-07-29 07:41:20 -05001476 for var in env_var_names_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301477 env_var = os.environ.get(var)
1478 if env_var:
1479 env_replace = "${" + var + "}"
1480 yaml_arg_str = yaml_arg_str.replace(env_replace, env_var)
George Keishingb97a9042021-07-29 07:41:20 -05001481 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001482 self.logger.error("\tERROR:yaml_env_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001483 pass
1484
George Keishinga593f4b2025-05-13 20:02:36 +05301485 """
1486 Parse the string for plugin vars.
1487 Implement the logic to update environment variables based on the
1488 extracted variable names.
1489 """
George Keishingb97a9042021-07-29 07:41:20 -05001490 try:
George Keishinga593f4b2025-05-13 20:02:36 +05301491 # Example, list of plugin vars env_var_names_list
1492 # ['my_hostname', 'port_https']
1493 global_plugin_dict_keys = set(global_plugin_dict.keys())
1494 # Skip env var list already populated above code block list.
1495 plugin_var_name_list = [
1496 var
1497 for var in global_plugin_dict_keys
1498 if var not in env_var_names_list
1499 ]
1500
George Keishingb97a9042021-07-29 07:41:20 -05001501 for var in plugin_var_name_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301502 plugin_var_value = global_plugin_dict[var]
George Keishing0581cb02021-08-05 15:08:58 -05001503 if yaml_arg_str in global_plugin_dict:
George Keishinga593f4b2025-05-13 20:02:36 +05301504 """
1505 If this plugin var exist but empty in dict, don't replace.
1506 his is either a YAML plugin statement incorrectly used or
1507 user added a plugin var which is not going to be populated.
1508 """
1509 if isinstance(plugin_var_value, (list, dict)):
1510 """
1511 List data type or dict can't be replaced, use
1512 directly in eval function call.
1513 """
George Keishing0581cb02021-08-05 15:08:58 -05001514 global_plugin_type_list.append(var)
1515 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001516 yaml_arg_str = yaml_arg_str.replace(
George Keishinga593f4b2025-05-13 20:02:36 +05301517 str(var), str(plugin_var_value)
Patrick Williams20f38712022-12-08 06:18:26 -06001518 )
George Keishingb97a9042021-07-29 07:41:20 -05001519 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001520 self.logger.error("\tERROR: yaml_plugin_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001521 pass
1522
George Keishinga593f4b2025-05-13 20:02:36 +05301523 # From ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
1524 # to populated values string as
1525 # Example: xx.xx.xx.xx:443 and return the string
George Keishingb97a9042021-07-29 07:41:20 -05001526 return yaml_arg_str
George Keishing1e7b0182021-08-06 14:05:54 -05001527
1528 def plugin_error_check(self, plugin_dict):
1529 r"""
George Keishing1e877422025-05-09 20:45:09 +05301530 Process plugin error dictionary and return the corresponding error
1531 message.
George Keishing1e7b0182021-08-06 14:05:54 -05001532
George Keishing1e877422025-05-09 20:45:09 +05301533 This method checks if any dictionary in the plugin_dict list contains
1534 a "plugin_error" key. If such a dictionary is found, it retrieves the
1535 value associated with the "plugin_error" key and returns the
1536 corresponding error message from the plugin_error_dict attribute.
1537
1538 Parameters:
1539 plugin_dict (list of dict): A list of dictionaries containing
1540 plugin error information.
1541
1542 Returns:
1543 str: The error message corresponding to the "plugin_error" value,
1544 or None if no error is found.
George Keishing1e7b0182021-08-06 14:05:54 -05001545 """
Patrick Williams20f38712022-12-08 06:18:26 -06001546 if any("plugin_error" in d for d in plugin_dict):
George Keishing1e7b0182021-08-06 14:05:54 -05001547 for d in plugin_dict:
Patrick Williams20f38712022-12-08 06:18:26 -06001548 if "plugin_error" in d:
1549 value = d["plugin_error"]
George Keishing1e877422025-05-09 20:45:09 +05301550 return self.plugin_error_dict.get(value, None)
1551 return None
George Keishingde79a9b2021-08-12 16:14:43 -05001552
1553 def key_index_list_dict(self, key, list_dict):
1554 r"""
George Keishing1e877422025-05-09 20:45:09 +05301555 Find the index of the first dictionary in the list that contains
1556 the specified key.
George Keishingde79a9b2021-08-12 16:14:43 -05001557
George Keishing1e877422025-05-09 20:45:09 +05301558 Parameters:
1559 key (str): The key to search for in the
1560 dictionaries.
1561 list_dict (list of dict): A list of dictionaries to search
1562 through.
1563
1564 Returns:
1565 int: The index of the first dictionary containing the key, or -1
1566 if no match is found.
George Keishingde79a9b2021-08-12 16:14:43 -05001567 """
1568 for i, d in enumerate(list_dict):
George Keishing1e877422025-05-09 20:45:09 +05301569 if key in d:
George Keishingde79a9b2021-08-12 16:14:43 -05001570 return i
George Keishing1e877422025-05-09 20:45:09 +05301571 return -1
George Keishingde79a9b2021-08-12 16:14:43 -05001572
1573 def plugin_expect_type(self, type, data):
1574 r"""
George Keishing1e877422025-05-09 20:45:09 +05301575 Check if the provided data matches the expected type.
1576
1577 This method checks if the data argument matches the specified type.
1578 It supports the following types: "int", "float", "str", "list", "dict",
1579 and "tuple".
1580
1581 If the type is not recognized, it logs an info message and returns
1582 "INVALID".
1583
1584 Parameters:
1585 type (str): The expected data type.
1586 data: The data to check against the expected type.
1587
1588 Returns:
1589 bool or str: True if the data matches the expected type, False if
1590 not, or "INVALID" if the type is not recognized.
George Keishingde79a9b2021-08-12 16:14:43 -05001591 """
Patrick Williams20f38712022-12-08 06:18:26 -06001592 if type == "int":
George Keishingde79a9b2021-08-12 16:14:43 -05001593 return isinstance(data, int)
Patrick Williams20f38712022-12-08 06:18:26 -06001594 elif type == "float":
George Keishingde79a9b2021-08-12 16:14:43 -05001595 return isinstance(data, float)
Patrick Williams20f38712022-12-08 06:18:26 -06001596 elif type == "str":
George Keishingde79a9b2021-08-12 16:14:43 -05001597 return isinstance(data, str)
Patrick Williams20f38712022-12-08 06:18:26 -06001598 elif type == "list":
George Keishingde79a9b2021-08-12 16:14:43 -05001599 return isinstance(data, list)
Patrick Williams20f38712022-12-08 06:18:26 -06001600 elif type == "dict":
George Keishingde79a9b2021-08-12 16:14:43 -05001601 return isinstance(data, dict)
Patrick Williams20f38712022-12-08 06:18:26 -06001602 elif type == "tuple":
George Keishingde79a9b2021-08-12 16:14:43 -05001603 return isinstance(data, tuple)
1604 else:
1605 self.logger.info("\tInvalid data type requested: %s" % type)
Patrick Williams20f38712022-12-08 06:18:26 -06001606 return "INVALID"