blob: 1467bfa78b4666cdf34b23096f1a675250680724 [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):
George Keishing967c1ed2025-05-17 16:32:41 +0530223 r"""
224 Verify that all required environment variables are set.
225
226 This method checks if all required environment variables are set.
227 If any required variable is missing, the method returns False.
228 Otherwise, it returns True.
229
230 Returns:
231 bool: True if all required environment variables are set,
232 False otherwise.
233 """
Peter D Phan72ce6b82021-06-03 06:18:26 -0500234 # Import to log version
235 import click
236 import paramiko
237
238 run_env_ok = True
Peter D Phan0c669772021-06-24 13:52:42 -0500239
George Keishingd805bc02025-02-28 12:17:13 +0530240 try:
241 redfishtool_version = (
242 self.run_tool_cmd("redfishtool -V").split(" ")[2].strip("\n")
243 )
244 except Exception as e:
245 self.logger.error("\tEXCEPTION redfishtool: %s", e)
246 redfishtool_version = "Not Installed (optional)"
247
248 try:
249 ipmitool_version = self.run_tool_cmd("ipmitool -V").split(" ")[2]
250 except Exception as e:
251 self.logger.error("\tEXCEPTION ipmitool: %s", e)
252 ipmitool_version = "Not Installed (optional)"
Peter D Phan0c669772021-06-24 13:52:42 -0500253
Peter D Phane86d9a52021-07-15 10:42:25 -0500254 self.logger.info("\n\t---- Script host environment ----")
Patrick Williams20f38712022-12-08 06:18:26 -0600255 self.logger.info(
256 "\t{:<10} {:<10}".format("Script hostname", os.uname()[1])
257 )
258 self.logger.info(
259 "\t{:<10} {:<10}".format("Script host os", platform.platform())
260 )
261 self.logger.info(
262 "\t{:<10} {:>10}".format("Python", platform.python_version())
263 )
264 self.logger.info("\t{:<10} {:>10}".format("PyYAML", yaml.__version__))
265 self.logger.info("\t{:<10} {:>10}".format("click", click.__version__))
266 self.logger.info(
267 "\t{:<10} {:>10}".format("paramiko", paramiko.__version__)
268 )
269 self.logger.info(
270 "\t{:<10} {:>9}".format("redfishtool", redfishtool_version)
271 )
272 self.logger.info(
273 "\t{:<10} {:>12}".format("ipmitool", ipmitool_version)
274 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500275
Patrick Williams20f38712022-12-08 06:18:26 -0600276 if eval(yaml.__version__.replace(".", ",")) < (5, 3, 0):
277 self.logger.error(
278 "\n\tERROR: Python or python packages do not meet minimum"
279 " version requirement."
280 )
281 self.logger.error(
282 "\tERROR: PyYAML version 5.3.0 or higher is needed.\n"
283 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500284 run_env_ok = False
285
Peter D Phane86d9a52021-07-15 10:42:25 -0500286 self.logger.info("\t---- End script host environment ----")
Peter D Phan72ce6b82021-06-03 06:18:26 -0500287 return run_env_ok
288
Patrick Williams20f38712022-12-08 06:18:26 -0600289 def script_logging(self, log_level_attr):
George Keishing967c1ed2025-05-17 16:32:41 +0530290 """
291 Create a logger for the script with the specified log level.
Peter D Phane86d9a52021-07-15 10:42:25 -0500292
George Keishing967c1ed2025-05-17 16:32:41 +0530293 This method creates a logger for the script with the specified
294 log level. The logger is configured to write log messages to a file
295 and the console.
296
297 self.logger = logging.getLogger(__name__)
298
299 Setting logger with __name__ will add the trace
300 Example:
301
302 INFO:ffdc_collector: System Type: OPENBMC
303
304 Currently, set to empty purposely to log as
305 System Type: OPENBMC
306
307 Parameters:
308 log_level_attr (str): The log level for the logger
309 (e.g., "DEBUG", "INFO", "WARNING",
310 "ERROR", "CRITICAL").
311
312 Returns:
313 None
Peter D Phane86d9a52021-07-15 10:42:25 -0500314 """
315 self.logger = logging.getLogger()
316 self.logger.setLevel(log_level_attr)
George Keishing967c1ed2025-05-17 16:32:41 +0530317
Patrick Williams20f38712022-12-08 06:18:26 -0600318 log_file_handler = logging.FileHandler(
319 self.ffdc_dir_path + "collector.log"
320 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500321 stdout_handler = logging.StreamHandler(sys.stdout)
George Keishing967c1ed2025-05-17 16:32:41 +0530322
Peter D Phane86d9a52021-07-15 10:42:25 -0500323 self.logger.addHandler(log_file_handler)
324 self.logger.addHandler(stdout_handler)
325
326 # Turn off paramiko INFO logging
327 logging.getLogger("paramiko").setLevel(logging.WARNING)
328
Peter D Phan72ce6b82021-06-03 06:18:26 -0500329 def target_is_pingable(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500330 r"""
George Keishing967c1ed2025-05-17 16:32:41 +0530331 Check if the target system is ping-able.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500332
George Keishing967c1ed2025-05-17 16:32:41 +0530333 This method checks if the target system is reachable by sending an
334 ICMP echo request (ping). If the target system responds to the ping,
335 the method returns True. Otherwise, it returns False.
336
337 Returns:
338 bool: True if the target system is ping-able, False otherwise.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500339 """
George Keishing967c1ed2025-05-17 16:32:41 +0530340 response = os.system("ping -c 2 %s 2>&1 >/dev/null" % self.hostname)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500341 if response == 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600342 self.logger.info(
343 "\n\t[Check] %s is ping-able.\t\t [OK]" % self.hostname
344 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500345 return True
346 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500347 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600348 "\n\tERROR: %s is not ping-able. FFDC collection aborted.\n"
349 % self.hostname
350 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500351 sys.exit(-1)
George Keishing967c1ed2025-05-17 16:32:41 +0530352 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500353
Peter D Phan72ce6b82021-06-03 06:18:26 -0500354 def collect_ffdc(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500355 r"""
George Keishing967c1ed2025-05-17 16:32:41 +0530356 Initiate FFDC collection based on the requested protocol.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500357
George Keishing967c1ed2025-05-17 16:32:41 +0530358 This method initiates FFDC (First Failure Data Capture) collection
359 based on the requested protocol (SSH,SCP, TELNET, REDFISH, IPMI).
360 The method establishes a connection to the target system using the
361 specified protocol and collects the required FFDC data.
362
363 Returns:
364 None
Peter D Phan72ce6b82021-06-03 06:18:26 -0500365 """
Patrick Williams20f38712022-12-08 06:18:26 -0600366 self.logger.info(
367 "\n\t---- Start communicating with %s ----" % self.hostname
368 )
Peter D Phan7610bc42021-07-06 06:31:05 -0500369 self.start_time = time.time()
Peter D Phan0c669772021-06-24 13:52:42 -0500370
George Keishingf5a57502021-07-22 16:43:47 -0500371 # Find the list of target and protocol supported.
372 check_protocol_list = []
373 config_dict = self.ffdc_actions
Peter D Phan0c669772021-06-24 13:52:42 -0500374
George Keishingf5a57502021-07-22 16:43:47 -0500375 for target_type in config_dict.keys():
376 if self.target_type != target_type:
377 continue
George Keishingeafba182021-06-29 13:44:58 -0500378
George Keishingf5a57502021-07-22 16:43:47 -0500379 for k, v in config_dict[target_type].items():
George Keishing967c1ed2025-05-17 16:32:41 +0530380 if v["PROTOCOL"][0] not in check_protocol_list:
381 check_protocol_list.append(v["PROTOCOL"][0])
Peter D Phanbff617a2021-07-22 08:41:35 -0500382
Patrick Williams20f38712022-12-08 06:18:26 -0600383 self.logger.info(
384 "\n\t %s protocol type: %s"
385 % (self.target_type, check_protocol_list)
386 )
Peter D Phanbff617a2021-07-22 08:41:35 -0500387
George Keishingf5a57502021-07-22 16:43:47 -0500388 verified_working_protocol = self.verify_protocol(check_protocol_list)
Peter D Phanbff617a2021-07-22 08:41:35 -0500389
George Keishingf5a57502021-07-22 16:43:47 -0500390 if verified_working_protocol:
Patrick Williams20f38712022-12-08 06:18:26 -0600391 self.logger.info(
392 "\n\t---- Completed protocol pre-requisite check ----\n"
393 )
Peter D Phan0c669772021-06-24 13:52:42 -0500394
George Keishingf5a57502021-07-22 16:43:47 -0500395 # Verify top level directory exists for storage
396 self.validate_local_store(self.location)
397
Patrick Williams20f38712022-12-08 06:18:26 -0600398 if (self.remote_protocol not in verified_working_protocol) and (
399 self.remote_protocol != "ALL"
400 ):
401 self.logger.info(
402 "\n\tWorking protocol list: %s" % verified_working_protocol
403 )
George Keishingf5a57502021-07-22 16:43:47 -0500404 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600405 "\tERROR: Requested protocol %s is not in working protocol"
George Keishing7899a452023-02-15 02:46:54 -0600406 " list.\n" % self.remote_protocol
Patrick Williams20f38712022-12-08 06:18:26 -0600407 )
George Keishingf5a57502021-07-22 16:43:47 -0500408 sys.exit(-1)
409 else:
410 self.generate_ffdc(verified_working_protocol)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500411
412 def ssh_to_target_system(self):
413 r"""
George Keishing04416092025-05-17 18:47:10 +0530414 Establish an SSH connection to the target system.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500415
George Keishing04416092025-05-17 18:47:10 +0530416 This method establishes an SSH connection to the target system using
417 the provided hostname, username, password, and SSH port. If the
418 connection is successful, the method returns True. Otherwise, it logs
419 an error message and returns False.
420
421 Returns:
422 bool: True if the connection is successful, False otherwise.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500423 """
424
Patrick Williams20f38712022-12-08 06:18:26 -0600425 self.ssh_remoteclient = SSHRemoteclient(
George Keishing7a61aa22023-06-26 13:18:37 +0530426 self.hostname, self.username, self.password, self.port_ssh
Patrick Williams20f38712022-12-08 06:18:26 -0600427 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500428
Peter D Phan5963d632021-07-12 09:58:55 -0500429 if self.ssh_remoteclient.ssh_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600430 self.logger.info(
431 "\n\t[Check] %s SSH connection established.\t [OK]"
432 % self.hostname
433 )
Peter D Phan733df632021-06-17 13:13:36 -0500434
Peter D Phan5963d632021-07-12 09:58:55 -0500435 # Check scp connection.
436 # If scp connection fails,
437 # continue with FFDC generation but skip scp files to local host.
438 self.ssh_remoteclient.scp_connection()
439 return True
440 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600441 self.logger.info(
442 "\n\t[Check] %s SSH connection.\t [NOT AVAILABLE]"
443 % self.hostname
444 )
Peter D Phan5963d632021-07-12 09:58:55 -0500445 return False
446
447 def telnet_to_target_system(self):
448 r"""
George Keishing04416092025-05-17 18:47:10 +0530449 Establish a Telnet connection to the target system.
450
451 This method establishes a Telnet connection to the target system using
452 the provided hostname, username, and Telnet port. If the connection is
453 successful, the method returns True. Otherwise, it logs an error
454 message and returns False.
455
456 Returns:
457 bool: True if the connection is successful, False otherwise.
Peter D Phan5963d632021-07-12 09:58:55 -0500458 """
Patrick Williams20f38712022-12-08 06:18:26 -0600459 self.telnet_remoteclient = TelnetRemoteclient(
460 self.hostname, self.username, self.password
461 )
Peter D Phan5963d632021-07-12 09:58:55 -0500462 if self.telnet_remoteclient.tn_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600463 self.logger.info(
464 "\n\t[Check] %s Telnet connection established.\t [OK]"
465 % self.hostname
466 )
Peter D Phan5963d632021-07-12 09:58:55 -0500467 return True
468 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600469 self.logger.info(
470 "\n\t[Check] %s Telnet connection.\t [NOT AVAILABLE]"
471 % self.hostname
472 )
Peter D Phan5963d632021-07-12 09:58:55 -0500473 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500474
George Keishing772c9772021-06-16 23:23:42 -0500475 def generate_ffdc(self, working_protocol_list):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500476 r"""
George Keishing04416092025-05-17 18:47:10 +0530477 Generate FFDC (First Failure Data Capture) based on the remote host
478 type and working protocols.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500479
George Keishing04416092025-05-17 18:47:10 +0530480 This method determines the actions to be performed for generating FFDC
481 based on the remote host type and the list of confirmed working
482 protocols. The method iterates through the available actions for the
483 remote host type and checks if any of the working protocols are
484 supported. If a supported protocol is found, the method executes the
485 corresponding FFDC generation action.
486
487 Parameters:
488 working_protocol_list (list): A list of confirmed working
489 protocols to connect to the
490 remote host.
491
492 Returns:
493 None
Peter D Phan72ce6b82021-06-03 06:18:26 -0500494 """
Patrick Williams20f38712022-12-08 06:18:26 -0600495 self.logger.info(
496 "\n\t---- Executing commands on " + self.hostname + " ----"
497 )
498 self.logger.info(
499 "\n\tWorking protocol list: %s" % working_protocol_list
500 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500501
George Keishingf5a57502021-07-22 16:43:47 -0500502 config_dict = self.ffdc_actions
503 for target_type in config_dict.keys():
504 if self.target_type != target_type:
George Keishing6ea92b02021-07-01 11:20:50 -0500505 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500506
Peter D Phane86d9a52021-07-15 10:42:25 -0500507 self.logger.info("\n\tFFDC Path: %s " % self.ffdc_dir_path)
Patrick Williams20f38712022-12-08 06:18:26 -0600508 global_plugin_dict["global_log_store_path"] = self.ffdc_dir_path
George Keishingf5a57502021-07-22 16:43:47 -0500509 self.logger.info("\tSystem Type: %s" % target_type)
510 for k, v in config_dict[target_type].items():
George Keishing04416092025-05-17 18:47:10 +0530511 protocol = v["PROTOCOL"][0]
512
Patrick Williams20f38712022-12-08 06:18:26 -0600513 if (
514 self.remote_protocol not in working_protocol_list
515 and self.remote_protocol != "ALL"
George Keishing04416092025-05-17 18:47:10 +0530516 ) or protocol not in working_protocol_list:
George Keishingf5a57502021-07-22 16:43:47 -0500517 continue
518
George Keishingb7607612021-07-27 13:31:23 -0500519 if protocol in working_protocol_list:
George Keishing04416092025-05-17 18:47:10 +0530520 if protocol in ["SSH", "SCP"]:
George Keishing12fd0652021-07-27 13:57:11 -0500521 self.protocol_ssh(protocol, target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600522 elif protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -0500523 self.protocol_telnet(target_type, k)
George Keishing04416092025-05-17 18:47:10 +0530524 elif protocol in ["REDFISH", "IPMI", "SHELL"]:
525 self.protocol_service_execute(protocol, target_type, k)
George Keishingb7607612021-07-27 13:31:23 -0500526 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600527 self.logger.error(
528 "\n\tERROR: %s is not available for %s."
529 % (protocol, self.hostname)
530 )
George Keishingeafba182021-06-29 13:44:58 -0500531
Peter D Phan04aca3b2021-06-21 10:37:18 -0500532 # Close network connection after collecting all files
Patrick Williams20f38712022-12-08 06:18:26 -0600533 self.elapsed_time = time.strftime(
534 "%H:%M:%S", time.gmtime(time.time() - self.start_time)
535 )
George Keishing48972ba2025-05-05 17:40:29 +0530536 self.logger.info("\n\tTotal time taken: %s" % self.elapsed_time)
Peter D Phanbff617a2021-07-22 08:41:35 -0500537 if self.ssh_remoteclient:
538 self.ssh_remoteclient.ssh_remoteclient_disconnect()
539 if self.telnet_remoteclient:
540 self.telnet_remoteclient.tn_remoteclient_disconnect()
Peter D Phan04aca3b2021-06-21 10:37:18 -0500541
Patrick Williams20f38712022-12-08 06:18:26 -0600542 def protocol_ssh(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500543 r"""
544 Perform actions using SSH and SCP protocols.
545
George Keishing04416092025-05-17 18:47:10 +0530546 This method executes a set of commands using the SSH protocol to
547 connect to the target system and collect FFDC data. The method takes
548 the protocol, target type, and sub-type as arguments and performs the
549 corresponding actions based on the provided parameters.
Peter D Phan0c669772021-06-24 13:52:42 -0500550
George Keishing04416092025-05-17 18:47:10 +0530551 Parameters:
552 protocol (str): The protocol to execute (SSH or SCP).
553 target_type (str): The type group of the remote host.
554 sub_type (str): The group type of commands to execute.
555
556 Returns:
557 None
558 """
Patrick Williams20f38712022-12-08 06:18:26 -0600559 if protocol == "SCP":
George Keishingf5a57502021-07-22 16:43:47 -0500560 self.group_copy(self.ffdc_actions[target_type][sub_type])
George Keishing6ea92b02021-07-01 11:20:50 -0500561 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600562 self.collect_and_copy_ffdc(
563 self.ffdc_actions[target_type][sub_type]
564 )
Peter D Phan0c669772021-06-24 13:52:42 -0500565
Patrick Williams20f38712022-12-08 06:18:26 -0600566 def protocol_telnet(self, target_type, sub_type):
Peter D Phan5963d632021-07-12 09:58:55 -0500567 r"""
George Keishing04416092025-05-17 18:47:10 +0530568 Perform actions using the Telnet protocol.
569
570 This method executes a set of commands using the Telnet protocol to
571 connect to the target system and collect FFDC data. The method takes
572 the target type and sub-type as arguments and performs the
573 corresponding actions based on the provided parameters.
574
575 Parameters:
576 target_type (str): The type group of the remote host.
577 sub_type (str): The group type of commands to execute.
578
579 Returns:
580 None
Peter D Phan5963d632021-07-12 09:58:55 -0500581 """
Patrick Williams20f38712022-12-08 06:18:26 -0600582 self.logger.info(
583 "\n\t[Run] Executing commands on %s using %s"
584 % (self.hostname, "TELNET")
585 )
Peter D Phan5963d632021-07-12 09:58:55 -0500586 telnet_files_saved = []
587 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600588 list_of_commands = self.ffdc_actions[target_type][sub_type]["COMMANDS"]
Peter D Phan5963d632021-07-12 09:58:55 -0500589 for index, each_cmd in enumerate(list_of_commands, start=0):
590 command_txt, command_timeout = self.unpack_command(each_cmd)
Patrick Williams20f38712022-12-08 06:18:26 -0600591 result = self.telnet_remoteclient.execute_command(
592 command_txt, command_timeout
593 )
Peter D Phan5963d632021-07-12 09:58:55 -0500594 if result:
595 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600596 targ_file = self.ffdc_actions[target_type][sub_type][
597 "FILES"
598 ][index]
Peter D Phan5963d632021-07-12 09:58:55 -0500599 except IndexError:
Peter D Phane86d9a52021-07-15 10:42:25 -0500600 targ_file = command_txt
601 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600602 "\n\t[WARN] Missing filename to store data from"
603 " telnet %s." % each_cmd
604 )
605 self.logger.warning(
606 "\t[WARN] Data will be stored in %s." % targ_file
607 )
608 targ_file_with_path = (
609 self.ffdc_dir_path + self.ffdc_prefix + targ_file
610 )
Peter D Phan5963d632021-07-12 09:58:55 -0500611 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600612 with open(targ_file_with_path, "w") as fp:
Peter D Phan5963d632021-07-12 09:58:55 -0500613 fp.write(result)
614 fp.close
615 telnet_files_saved.append(targ_file)
616 progress_counter += 1
617 self.print_progress(progress_counter)
Peter D Phane86d9a52021-07-15 10:42:25 -0500618 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan5963d632021-07-12 09:58:55 -0500619 for file in telnet_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500620 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
Peter D Phan5963d632021-07-12 09:58:55 -0500621
George Keishing04416092025-05-17 18:47:10 +0530622 def protocol_service_execute(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500623 r"""
George Keishing506b0582021-07-27 09:31:22 -0500624 Perform actions for a given protocol.
Peter D Phan0c669772021-06-24 13:52:42 -0500625
George Keishing04416092025-05-17 18:47:10 +0530626 This method executes a set of commands using the specified protocol to
627 connect to the target system and collect FFDC data. The method takes
628 the protocol, target type, and sub-type as arguments and performs the
629 corresponding actions based on the provided parameters.
Peter D Phan0c669772021-06-24 13:52:42 -0500630
George Keishing04416092025-05-17 18:47:10 +0530631 Parameters:
632 protocol (str): The protocol to execute
633 (REDFISH, IPMI, or SHELL).
634 target_type (str): The type group of the remote host.
635 sub_type (str): The group type of commands to execute.
636
637 Returns:
638 None
639 """
Patrick Williams20f38712022-12-08 06:18:26 -0600640 self.logger.info(
641 "\n\t[Run] Executing commands to %s using %s"
642 % (self.hostname, protocol)
643 )
George Keishing506b0582021-07-27 09:31:22 -0500644 executed_files_saved = []
George Keishingeafba182021-06-29 13:44:58 -0500645 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600646 list_of_cmd = self.get_command_list(
647 self.ffdc_actions[target_type][sub_type]
648 )
George Keishingeafba182021-06-29 13:44:58 -0500649 for index, each_cmd in enumerate(list_of_cmd, start=0):
George Keishingcaa97e62021-08-03 14:00:09 -0500650 plugin_call = False
George Keishingb97a9042021-07-29 07:41:20 -0500651 if isinstance(each_cmd, dict):
Patrick Williams20f38712022-12-08 06:18:26 -0600652 if "plugin" in each_cmd:
George Keishing1e7b0182021-08-06 14:05:54 -0500653 # If the error is set and plugin explicitly
654 # requested to skip execution on error..
Patrick Williams20f38712022-12-08 06:18:26 -0600655 if plugin_error_dict[
656 "exit_on_error"
657 ] and self.plugin_error_check(each_cmd["plugin"]):
658 self.logger.info(
659 "\n\t[PLUGIN-ERROR] exit_on_error: %s"
660 % plugin_error_dict["exit_on_error"]
661 )
662 self.logger.info(
663 "\t[PLUGIN-SKIP] %s" % each_cmd["plugin"][0]
664 )
George Keishing1e7b0182021-08-06 14:05:54 -0500665 continue
George Keishingcaa97e62021-08-03 14:00:09 -0500666 plugin_call = True
George Keishingb97a9042021-07-29 07:41:20 -0500667 # call the plugin
668 self.logger.info("\n\t[PLUGIN-START]")
Patrick Williams20f38712022-12-08 06:18:26 -0600669 result = self.execute_plugin_block(each_cmd["plugin"])
George Keishingb97a9042021-07-29 07:41:20 -0500670 self.logger.info("\t[PLUGIN-END]\n")
George Keishingb97a9042021-07-29 07:41:20 -0500671 else:
George Keishing2b83e042021-08-03 12:56:11 -0500672 each_cmd = self.yaml_env_and_plugin_vars_populate(each_cmd)
George Keishingb97a9042021-07-29 07:41:20 -0500673
George Keishingcaa97e62021-08-03 14:00:09 -0500674 if not plugin_call:
675 result = self.run_tool_cmd(each_cmd)
George Keishingeafba182021-06-29 13:44:58 -0500676 if result:
677 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600678 file_name = self.get_file_list(
679 self.ffdc_actions[target_type][sub_type]
680 )[index]
George Keishingb97a9042021-07-29 07:41:20 -0500681 # If file is specified as None.
George Keishing0581cb02021-08-05 15:08:58 -0500682 if file_name == "None":
George Keishingb97a9042021-07-29 07:41:20 -0500683 continue
Patrick Williams20f38712022-12-08 06:18:26 -0600684 targ_file = self.yaml_env_and_plugin_vars_populate(
685 file_name
686 )
George Keishingeafba182021-06-29 13:44:58 -0500687 except IndexError:
Patrick Williams20f38712022-12-08 06:18:26 -0600688 targ_file = each_cmd.split("/")[-1]
George Keishing506b0582021-07-27 09:31:22 -0500689 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600690 "\n\t[WARN] Missing filename to store data from %s."
691 % each_cmd
692 )
693 self.logger.warning(
694 "\t[WARN] Data will be stored in %s." % targ_file
695 )
George Keishingeafba182021-06-29 13:44:58 -0500696
Patrick Williams20f38712022-12-08 06:18:26 -0600697 targ_file_with_path = (
698 self.ffdc_dir_path + self.ffdc_prefix + targ_file
699 )
George Keishingeafba182021-06-29 13:44:58 -0500700
701 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600702 with open(targ_file_with_path, "w") as fp:
George Keishing91308ea2021-08-10 14:43:15 -0500703 if isinstance(result, dict):
704 fp.write(json.dumps(result))
705 else:
706 fp.write(result)
George Keishingeafba182021-06-29 13:44:58 -0500707 fp.close
George Keishing506b0582021-07-27 09:31:22 -0500708 executed_files_saved.append(targ_file)
George Keishingeafba182021-06-29 13:44:58 -0500709
710 progress_counter += 1
711 self.print_progress(progress_counter)
712
Peter D Phane86d9a52021-07-15 10:42:25 -0500713 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
George Keishingeafba182021-06-29 13:44:58 -0500714
George Keishing506b0582021-07-27 09:31:22 -0500715 for file in executed_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500716 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
George Keishingeafba182021-06-29 13:44:58 -0500717
Patrick Williams20f38712022-12-08 06:18:26 -0600718 def collect_and_copy_ffdc(
719 self, ffdc_actions_for_target_type, form_filename=False
720 ):
Peter D Phan04aca3b2021-06-21 10:37:18 -0500721 r"""
George Keishing04416092025-05-17 18:47:10 +0530722 Send commands and collect FFDC data from the targeted system.
Peter D Phan04aca3b2021-06-21 10:37:18 -0500723
George Keishing04416092025-05-17 18:47:10 +0530724 This method sends a set of commands and collects FFDC data from the
725 targeted system based on the provided ffdc_actions_for_target_type
726 dictionary. The method also has an optional form_filename parameter,
727 which, if set to True, prepends the target type to the output file
728 name.
729
730 Parameters:
731 ffdc_actions_for_target_type (dict): A dictionary containing
732 commands and files for the
733 selected remote host type.
734 form_filename (bool, optional): If True, prepends the target
735 type to the output file name.
736 Defaults to False.
737
738 Returns:
739 None
Peter D Phan04aca3b2021-06-21 10:37:18 -0500740 """
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500741 # Executing commands, if any
Patrick Williams20f38712022-12-08 06:18:26 -0600742 self.ssh_execute_ffdc_commands(
743 ffdc_actions_for_target_type, form_filename
744 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500745
Peter D Phan3beb02e2021-07-06 13:25:17 -0500746 # Copying files
Peter D Phan5963d632021-07-12 09:58:55 -0500747 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600748 self.logger.info(
749 "\n\n\tCopying FFDC files from remote system %s.\n"
750 % self.hostname
751 )
Peter D Phan2b8052d2021-06-22 10:55:41 -0500752
Peter D Phan04aca3b2021-06-21 10:37:18 -0500753 # Retrieving files from target system
George Keishingf5a57502021-07-22 16:43:47 -0500754 list_of_files = self.get_file_list(ffdc_actions_for_target_type)
Patrick Williams20f38712022-12-08 06:18:26 -0600755 self.scp_ffdc(
756 self.ffdc_dir_path,
757 self.ffdc_prefix,
758 form_filename,
759 list_of_files,
760 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500761 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600762 self.logger.info(
763 "\n\n\tSkip copying FFDC files from remote system %s.\n"
764 % self.hostname
765 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500766
Patrick Williams20f38712022-12-08 06:18:26 -0600767 def get_command_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500768 r"""
George Keishing04416092025-05-17 18:47:10 +0530769 Fetch a list of commands from the configuration file.
Peter D Phanbabf2962021-07-07 11:24:40 -0500770
George Keishing04416092025-05-17 18:47:10 +0530771 This method retrieves a list of commands from the
772 ffdc_actions_for_target_type dictionary, which contains commands and
773 files for the selected remote host type. The method returns the list
774 of commands.
775
776 Parameters:
777 ffdc_actions_for_target_type (dict): A dictionary containing
778 commands and files for the
779 selected remote host type.
780
781 Returns:
782 list: A list of commands.
Peter D Phanbabf2962021-07-07 11:24:40 -0500783 """
784 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600785 list_of_commands = ffdc_actions_for_target_type["COMMANDS"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500786 except KeyError:
787 list_of_commands = []
788 return list_of_commands
789
Patrick Williams20f38712022-12-08 06:18:26 -0600790 def get_file_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500791 r"""
George Keishing04416092025-05-17 18:47:10 +0530792 Fetch a list of files from the configuration file.
Peter D Phanbabf2962021-07-07 11:24:40 -0500793
George Keishing04416092025-05-17 18:47:10 +0530794 This method retrieves a list of files from the
795 ffdc_actions_for_target_type dictionary, which contains commands and
796 files for the selected remote host type. The method returns the list
797 of files.
798
799 Parameters:
800 ffdc_actions_for_target_type (dict): A dictionary containing
801 commands and files for the
802 selected remote host type.
803
804 Returns:
805 list: A list of files.
Peter D Phanbabf2962021-07-07 11:24:40 -0500806 """
807 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600808 list_of_files = ffdc_actions_for_target_type["FILES"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500809 except KeyError:
810 list_of_files = []
811 return list_of_files
812
Patrick Williams20f38712022-12-08 06:18:26 -0600813 def unpack_command(self, command):
Peter D Phan5963d632021-07-12 09:58:55 -0500814 r"""
George Keishing04416092025-05-17 18:47:10 +0530815 Unpack a command from the configuration file, handling both dictionary
816 and string inputs.
Peter D Phan5963d632021-07-12 09:58:55 -0500817
George Keishing04416092025-05-17 18:47:10 +0530818 This method takes a command from the configuration file, which can be
819 either a dictionary or a string. If the input is a dictionary, the
820 method extracts the command text and timeout from the dictionary.
821 If the input is a string, the method assumes a default timeout of
822 60 seconds.
823 The method returns a tuple containing the command text and timeout.
824
825 Parameters:
826 command (dict or str): A command from the configuration file,
827 which can be either a dictionary or a
828 string.
829
830 Returns:
831 tuple: A tuple containing the command text and timeout.
Peter D Phan5963d632021-07-12 09:58:55 -0500832 """
833 if isinstance(command, dict):
834 command_txt = next(iter(command))
835 command_timeout = next(iter(command.values()))
836 elif isinstance(command, str):
837 command_txt = command
838 # Default command timeout 60 seconds
839 command_timeout = 60
840
841 return command_txt, command_timeout
842
Patrick Williams20f38712022-12-08 06:18:26 -0600843 def ssh_execute_ffdc_commands(
844 self, ffdc_actions_for_target_type, form_filename=False
845 ):
Peter D Phan3beb02e2021-07-06 13:25:17 -0500846 r"""
George Keishing04416092025-05-17 18:47:10 +0530847 Send commands in the ffdc_config file to the targeted system using SSH.
Peter D Phan3beb02e2021-07-06 13:25:17 -0500848
George Keishing04416092025-05-17 18:47:10 +0530849 This method sends a set of commands and collects FFDC data from the
850 targeted system using the SSH protocol. The method takes the
851 ffdc_actions_for_target_type dictionary and an optional
852 form_filename parameter as arguments.
853
854 If form_filename is set to True, the method prepends the target type
855 to the output file name. The method returns the output of the executed
856 commands.
857
858 It also prints the progress counter string + on the console.
859
860 Parameters:
861 ffdc_actions_for_target_type (dict): A dictionary containing
862 commands and files for the
863 selected remote host type.
864 form_filename (bool, optional): If True, prepends the target
865 type to the output file name.
866 Defaults to False.
867
868 Returns:
869 None
Peter D Phan3beb02e2021-07-06 13:25:17 -0500870 """
Patrick Williams20f38712022-12-08 06:18:26 -0600871 self.logger.info(
872 "\n\t[Run] Executing commands on %s using %s"
873 % (self.hostname, ffdc_actions_for_target_type["PROTOCOL"][0])
874 )
Peter D Phan3beb02e2021-07-06 13:25:17 -0500875
George Keishingf5a57502021-07-22 16:43:47 -0500876 list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500877 # If command list is empty, returns
878 if not list_of_commands:
879 return
880
881 progress_counter = 0
882 for command in list_of_commands:
Peter D Phan5963d632021-07-12 09:58:55 -0500883 command_txt, command_timeout = self.unpack_command(command)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500884
885 if form_filename:
886 command_txt = str(command_txt % self.target_type)
887
Patrick Williams20f38712022-12-08 06:18:26 -0600888 (
889 cmd_exit_code,
890 err,
891 response,
892 ) = self.ssh_remoteclient.execute_command(
893 command_txt, command_timeout
894 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500895
896 if cmd_exit_code:
897 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600898 "\n\t\t[WARN] %s exits with code %s."
899 % (command_txt, str(cmd_exit_code))
900 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500901 self.logger.warning("\t\t[WARN] %s " % err)
Peter D Phanbabf2962021-07-07 11:24:40 -0500902
Peter D Phan3beb02e2021-07-06 13:25:17 -0500903 progress_counter += 1
904 self.print_progress(progress_counter)
905
Peter D Phane86d9a52021-07-15 10:42:25 -0500906 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan3beb02e2021-07-06 13:25:17 -0500907
Patrick Williams20f38712022-12-08 06:18:26 -0600908 def group_copy(self, ffdc_actions_for_target_type):
Peter D Phan56429a62021-06-23 08:38:29 -0500909 r"""
George Keishing04416092025-05-17 18:47:10 +0530910 SCP a group of files (wildcard) from the remote host.
Peter D Phan56429a62021-06-23 08:38:29 -0500911
George Keishing04416092025-05-17 18:47:10 +0530912 This method copies a group of files from the remote host using the SCP
913 protocol. The method takes the fdc_actions_for_target_type dictionary
914 as an argument, which contains commands and files for the selected
915 remote host type.
916
917 Parameters:
918 fdc_actions_for_target_type (dict): A dictionary containing
919 commands and files for the
920 selected remote host type.
921
922 Returns:
923 None
Peter D Phan56429a62021-06-23 08:38:29 -0500924 """
Peter D Phan5963d632021-07-12 09:58:55 -0500925 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600926 self.logger.info(
927 "\n\tCopying files from remote system %s via SCP.\n"
928 % self.hostname
929 )
Peter D Phan56429a62021-06-23 08:38:29 -0500930
Patrick Williams20f38712022-12-08 06:18:26 -0600931 list_of_commands = self.get_command_list(
932 ffdc_actions_for_target_type
933 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500934 # If command list is empty, returns
935 if not list_of_commands:
936 return
Peter D Phan56429a62021-06-23 08:38:29 -0500937
Peter D Phanbabf2962021-07-07 11:24:40 -0500938 for command in list_of_commands:
939 try:
George Keishingb4540e72021-08-02 13:48:46 -0500940 command = self.yaml_env_and_plugin_vars_populate(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500941 except IndexError:
George Keishingb4540e72021-08-02 13:48:46 -0500942 self.logger.error("\t\tInvalid command %s" % command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500943 continue
944
Patrick Williams20f38712022-12-08 06:18:26 -0600945 (
946 cmd_exit_code,
947 err,
948 response,
949 ) = self.ssh_remoteclient.execute_command(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500950
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500951 # If file does not exist, code take no action.
952 # cmd_exit_code is ignored for this scenario.
Peter D Phan56429a62021-06-23 08:38:29 -0500953 if response:
Patrick Williams20f38712022-12-08 06:18:26 -0600954 scp_result = self.ssh_remoteclient.scp_file_from_remote(
955 response.split("\n"), self.ffdc_dir_path
956 )
Peter D Phan56429a62021-06-23 08:38:29 -0500957 if scp_result:
Patrick Williams20f38712022-12-08 06:18:26 -0600958 self.logger.info(
959 "\t\tSuccessfully copied from "
960 + self.hostname
961 + ":"
962 + command
963 )
Peter D Phan56429a62021-06-23 08:38:29 -0500964 else:
George Keishinga56e87b2021-08-06 00:24:19 -0500965 self.logger.info("\t\t%s has no result" % command)
Peter D Phan56429a62021-06-23 08:38:29 -0500966
967 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600968 self.logger.info(
969 "\n\n\tSkip copying files from remote system %s.\n"
970 % self.hostname
971 )
Peter D Phan56429a62021-06-23 08:38:29 -0500972
Patrick Williams20f38712022-12-08 06:18:26 -0600973 def scp_ffdc(
974 self,
975 targ_dir_path,
976 targ_file_prefix,
977 form_filename,
978 file_list=None,
979 quiet=None,
980 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500981 r"""
George Keishing04416092025-05-17 18:47:10 +0530982 SCP all files in the file_dict to the indicated directory on the local
George Keishingc754b432025-04-24 14:27:14 +0530983 system.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500984
George Keishing04416092025-05-17 18:47:10 +0530985 This method copies all files specified in the file_dict dictionary
986 from the targeted system to the local system using the SCP protocol.
987 The method takes the target directory path, target file prefix, and a
988 boolean flag form_filename as required arguments.
989
990 The file_dict argument is optional and contains the files to be copied.
991 The quiet argument is also optional and, if set to True, suppresses
992 the output of the SCP operation.
993
994 Parameters:
995 targ_dir_path (str): The path of the directory to receive
996 the files on the local system.
997 targ_file_prefix (str): Prefix which will be prepended to each
Peter D Phan72ce6b82021-06-03 06:18:26 -0500998 target file's name.
George Keishing04416092025-05-17 18:47:10 +0530999 form_filename (bool): If True, prepends the target type to
1000 the file names.
1001 file_dict (dict, optional): A dictionary containing the files to
1002 be copied. Defaults to None.
1003 quiet (bool, optional): If True, suppresses the output of the
1004 SCP operation. Defaults to None.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001005
George Keishing04416092025-05-17 18:47:10 +05301006 Returns:
1007 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001008 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001009 progress_counter = 0
1010 for filename in file_list:
Peter D Phan2b8052d2021-06-22 10:55:41 -05001011 if form_filename:
1012 filename = str(filename % self.target_type)
Peter D Phan72ce6b82021-06-03 06:18:26 -05001013 source_file_path = filename
Patrick Williams20f38712022-12-08 06:18:26 -06001014 targ_file_path = (
1015 targ_dir_path + targ_file_prefix + filename.split("/")[-1]
1016 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001017
Peter D Phanbabf2962021-07-07 11:24:40 -05001018 # If source file name contains wild card, copy filename as is.
Patrick Williams20f38712022-12-08 06:18:26 -06001019 if "*" in source_file_path:
1020 scp_result = self.ssh_remoteclient.scp_file_from_remote(
1021 source_file_path, self.ffdc_dir_path
1022 )
Peter D Phanbabf2962021-07-07 11:24:40 -05001023 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001024 scp_result = self.ssh_remoteclient.scp_file_from_remote(
1025 source_file_path, targ_file_path
1026 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001027
1028 if not quiet:
1029 if scp_result:
Peter D Phane86d9a52021-07-15 10:42:25 -05001030 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -06001031 "\t\tSuccessfully copied from "
1032 + self.hostname
1033 + ":"
1034 + source_file_path
1035 + ".\n"
1036 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001037 else:
Peter D Phane86d9a52021-07-15 10:42:25 -05001038 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -06001039 "\t\tFail to copy from "
1040 + self.hostname
1041 + ":"
1042 + source_file_path
1043 + ".\n"
1044 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001045 else:
1046 progress_counter += 1
1047 self.print_progress(progress_counter)
1048
Peter D Phan5e56f522021-12-20 13:19:41 -06001049 def set_ffdc_default_store_path(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -05001050 r"""
George Keishing04416092025-05-17 18:47:10 +05301051 Set default values for self.ffdc_dir_path and self.ffdc_prefix.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001052
George Keishing04416092025-05-17 18:47:10 +05301053 This method sets default values for the self.ffdc_dir_path and
1054 self.ffdc_prefix class variables.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001055
George Keishing04416092025-05-17 18:47:10 +05301056 The collected FFDC files will be stored in the directory
1057 /self.location/hostname_timestr/, with individual files having the
1058 format timestr_filename where timestr is in %Y%m%d-%H%M%S.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001059
George Keishing04416092025-05-17 18:47:10 +05301060 Returns:
1061 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001062 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001063 timestr = time.strftime("%Y%m%d-%H%M%S")
Patrick Williams20f38712022-12-08 06:18:26 -06001064 self.ffdc_dir_path = (
1065 self.location + "/" + self.hostname + "_" + timestr + "/"
1066 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001067 self.ffdc_prefix = timestr + "_"
1068 self.validate_local_store(self.ffdc_dir_path)
1069
Peter D Phan5e56f522021-12-20 13:19:41 -06001070 # Need to verify local store path exists prior to instantiate this class.
George Keishing04416092025-05-17 18:47:10 +05301071 # This class method to validate log path before referencing this class.
Peter D Phan5e56f522021-12-20 13:19:41 -06001072 @classmethod
1073 def validate_local_store(cls, dir_path):
Peter D Phan72ce6b82021-06-03 06:18:26 -05001074 r"""
George Keishing04416092025-05-17 18:47:10 +05301075 Ensure the specified directory exists to store FFDC files locally.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001076
George Keishing04416092025-05-17 18:47:10 +05301077 This method checks if the provided dir_path exists. If the directory
1078 does not exist, the method creates it. The method does not return any
1079 value.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001080
George Keishing04416092025-05-17 18:47:10 +05301081 Parameters:
1082 dir_path (str): The directory path where collected FFDC data files
1083 will be stored.
1084
1085 Returns:
1086 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001087 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001088 if not os.path.exists(dir_path):
1089 try:
George Keishing7b3a5132021-07-13 09:24:02 -05001090 os.makedirs(dir_path, 0o755)
Peter D Phan72ce6b82021-06-03 06:18:26 -05001091 except (IOError, OSError) as e:
1092 # PermissionError
1093 if e.errno == EPERM or e.errno == EACCES:
George Keishing15352052025-04-24 18:55:47 +05301094 print(
Patrick Williams20f38712022-12-08 06:18:26 -06001095 "\tERROR: os.makedirs %s failed with"
1096 " PermissionError.\n" % dir_path
1097 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001098 else:
George Keishing15352052025-04-24 18:55:47 +05301099 print(
Patrick Williams20f38712022-12-08 06:18:26 -06001100 "\tERROR: os.makedirs %s failed with %s.\n"
1101 % (dir_path, e.strerror)
1102 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001103 sys.exit(-1)
1104
1105 def print_progress(self, progress):
1106 r"""
George Keishing04416092025-05-17 18:47:10 +05301107 Print the current activity progress.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001108
George Keishing04416092025-05-17 18:47:10 +05301109 This method prints the current activity progress using the provided
1110 progress counter. The method does not return any value.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001111
George Keishing04416092025-05-17 18:47:10 +05301112 Parameters:
1113 progress (int): The current activity progress counter.
1114
1115 Returns:
1116 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001117 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001118 sys.stdout.write("\r\t" + "+" * progress)
1119 sys.stdout.flush()
Patrick Williams20f38712022-12-08 06:18:26 -06001120 time.sleep(0.1)
Peter D Phan0c669772021-06-24 13:52:42 -05001121
1122 def verify_redfish(self):
1123 r"""
George Keishing04416092025-05-17 18:47:10 +05301124 Verify if the remote host has the Redfish service active.
Peter D Phan0c669772021-06-24 13:52:42 -05001125
George Keishing04416092025-05-17 18:47:10 +05301126 This method checks if the remote host has the Redfish service active
1127 by sending a GET request to the Redfish base URL /redfish/v1/.
1128 If the request is successful (status code 200), the method returns
1129 stdout output of the run else error message.
1130
1131 Returns:
1132 str: Redfish service executed output.
Peter D Phan0c669772021-06-24 13:52:42 -05001133 """
Patrick Williams20f38712022-12-08 06:18:26 -06001134 redfish_parm = (
1135 "redfishtool -r "
1136 + self.hostname
George Keishing7a61aa22023-06-26 13:18:37 +05301137 + ":"
1138 + self.port_https
Patrick Williams20f38712022-12-08 06:18:26 -06001139 + " -S Always raw GET /redfish/v1/"
1140 )
1141 return self.run_tool_cmd(redfish_parm, True)
Peter D Phan0c669772021-06-24 13:52:42 -05001142
George Keishingeafba182021-06-29 13:44:58 -05001143 def verify_ipmi(self):
1144 r"""
George Keishing04416092025-05-17 18:47:10 +05301145 Verify if the remote host has the IPMI LAN service active.
George Keishingeafba182021-06-29 13:44:58 -05001146
George Keishing04416092025-05-17 18:47:10 +05301147 This method checks if the remote host has the IPMI LAN service active
1148 by sending an IPMI "power status" command.
1149
1150 If the command is successful (returns a non-empty response),
1151 else error message.
1152
1153 Returns:
1154 str: IPMI LAN service executed output.
George Keishingeafba182021-06-29 13:44:58 -05001155 """
Patrick Williams20f38712022-12-08 06:18:26 -06001156 if self.target_type == "OPENBMC":
1157 ipmi_parm = (
1158 "ipmitool -I lanplus -C 17 -U "
1159 + self.username
1160 + " -P "
1161 + self.password
1162 + " -H "
1163 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +05301164 + " -p "
1165 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -06001166 + " power status"
1167 )
George Keishing484f8242021-07-27 01:42:02 -05001168 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001169 ipmi_parm = (
1170 "ipmitool -I lanplus -P "
1171 + self.password
1172 + " -H "
1173 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +05301174 + " -p "
1175 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -06001176 + " power status"
1177 )
George Keishing484f8242021-07-27 01:42:02 -05001178
Patrick Williams20f38712022-12-08 06:18:26 -06001179 return self.run_tool_cmd(ipmi_parm, True)
George Keishingeafba182021-06-29 13:44:58 -05001180
Patrick Williams20f38712022-12-08 06:18:26 -06001181 def run_tool_cmd(self, parms_string, quiet=False):
George Keishingeafba182021-06-29 13:44:58 -05001182 r"""
George Keishing04416092025-05-17 18:47:10 +05301183 Run a CLI standard tool or script with the provided command options.
George Keishingeafba182021-06-29 13:44:58 -05001184
George Keishing04416092025-05-17 18:47:10 +05301185 This method runs a CLI standard tool or script with the provided
1186 parms_string command options. If the quiet parameter is set to True,
1187 the method suppresses the output of the command.
1188 The method returns the output of the command as a string.
1189
1190 Parameters:
1191 parms_string (str): The command options for the CLI tool or
1192 script.
1193 quiet (bool, optional): If True, suppresses the output of the
1194 command. Defaults to False.
1195
1196 Returns:
1197 str: The output of the command as a string.
George Keishingeafba182021-06-29 13:44:58 -05001198 """
1199
Patrick Williams20f38712022-12-08 06:18:26 -06001200 result = subprocess.run(
1201 [parms_string],
1202 stdout=subprocess.PIPE,
1203 stderr=subprocess.PIPE,
1204 shell=True,
1205 universal_newlines=True,
1206 )
George Keishingeafba182021-06-29 13:44:58 -05001207
1208 if result.stderr and not quiet:
George Keishing0e9b5ba2025-05-08 12:17:58 +05301209 if self.password in parms_string:
1210 parms_string = parms_string.replace(self.password, "********")
Patrick Williams20f38712022-12-08 06:18:26 -06001211 self.logger.error("\n\t\tERROR with %s " % parms_string)
1212 self.logger.error("\t\t" + result.stderr)
George Keishingeafba182021-06-29 13:44:58 -05001213
1214 return result.stdout
George Keishing04d29102021-07-16 02:05:57 -05001215
George Keishingf5a57502021-07-22 16:43:47 -05001216 def verify_protocol(self, protocol_list):
1217 r"""
George Keishing04416092025-05-17 18:47:10 +05301218 Perform a working check for the provided list of protocols.
George Keishingf5a57502021-07-22 16:43:47 -05001219
George Keishing04416092025-05-17 18:47:10 +05301220 This method checks if the specified protocols are available on the
1221 remote host. The method iterates through the protocol_list and
1222 attempts to establish a connection using each protocol.
1223
1224 If a connection is successfully established, the method append to the
1225 list and if any protocol fails to connect, the method ignores it.
1226
1227 Parameters:
1228 protocol_list (list): A list of protocols to check.
1229
1230 Returns:
1231 list: All protocols are available list.
George Keishingf5a57502021-07-22 16:43:47 -05001232 """
1233
1234 tmp_list = []
1235 if self.target_is_pingable():
1236 tmp_list.append("SHELL")
1237
1238 for protocol in protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -06001239 if self.remote_protocol != "ALL":
George Keishingf5a57502021-07-22 16:43:47 -05001240 if self.remote_protocol != protocol:
1241 continue
1242
1243 # Only check SSH/SCP once for both protocols
Patrick Williams20f38712022-12-08 06:18:26 -06001244 if (
1245 protocol == "SSH"
1246 or protocol == "SCP"
1247 and protocol not in tmp_list
1248 ):
George Keishingf5a57502021-07-22 16:43:47 -05001249 if self.ssh_to_target_system():
George Keishingaa638702021-07-26 11:48:28 -05001250 # Add only what user asked.
Patrick Williams20f38712022-12-08 06:18:26 -06001251 if self.remote_protocol != "ALL":
George Keishingaa638702021-07-26 11:48:28 -05001252 tmp_list.append(self.remote_protocol)
1253 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001254 tmp_list.append("SSH")
1255 tmp_list.append("SCP")
George Keishingf5a57502021-07-22 16:43:47 -05001256
Patrick Williams20f38712022-12-08 06:18:26 -06001257 if protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -05001258 if self.telnet_to_target_system():
1259 tmp_list.append(protocol)
1260
Patrick Williams20f38712022-12-08 06:18:26 -06001261 if protocol == "REDFISH":
George Keishingf5a57502021-07-22 16:43:47 -05001262 if self.verify_redfish():
1263 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001264 self.logger.info(
1265 "\n\t[Check] %s Redfish Service.\t\t [OK]"
1266 % self.hostname
1267 )
George Keishingf5a57502021-07-22 16:43:47 -05001268 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001269 self.logger.info(
1270 "\n\t[Check] %s Redfish Service.\t\t [NOT AVAILABLE]"
1271 % self.hostname
1272 )
George Keishingf5a57502021-07-22 16:43:47 -05001273
Patrick Williams20f38712022-12-08 06:18:26 -06001274 if protocol == "IPMI":
George Keishingf5a57502021-07-22 16:43:47 -05001275 if self.verify_ipmi():
1276 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001277 self.logger.info(
1278 "\n\t[Check] %s IPMI LAN Service.\t\t [OK]"
1279 % self.hostname
1280 )
George Keishingf5a57502021-07-22 16:43:47 -05001281 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001282 self.logger.info(
1283 "\n\t[Check] %s IPMI LAN Service.\t\t [NOT AVAILABLE]"
1284 % self.hostname
1285 )
George Keishingf5a57502021-07-22 16:43:47 -05001286
1287 return tmp_list
George Keishinge1686752021-07-27 12:55:28 -05001288
1289 def load_env(self):
1290 r"""
George Keishing0e9b5ba2025-05-08 12:17:58 +05301291 Load the user environment variables from a YAML file.
George Keishinge1686752021-07-27 12:55:28 -05001292
George Keishing0e9b5ba2025-05-08 12:17:58 +05301293 This method reads the environment variables from a YAML file specified
1294 in the ENV_FILE environment variable. If the file is not found or
1295 there is an error reading the file, an exception is raised.
1296
1297 The YAML file should have the following format:
1298
1299 .. code-block:: yaml
1300
1301 VAR_NAME: VAR_VALUE
1302
1303 Where VAR_NAME is the name of the environment variable, and
1304 VAR_VALUE is its value.
1305
1306 After loading the environment variables, they are stored in the
1307 self.env attribute for later use.
George Keishinge1686752021-07-27 12:55:28 -05001308 """
George Keishing0e9b5ba2025-05-08 12:17:58 +05301309
Patrick Williams20f38712022-12-08 06:18:26 -06001310 os.environ["hostname"] = self.hostname
1311 os.environ["username"] = self.username
1312 os.environ["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301313 os.environ["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301314 os.environ["port_https"] = self.port_https
1315 os.environ["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001316
1317 # Append default Env.
Patrick Williams20f38712022-12-08 06:18:26 -06001318 self.env_dict["hostname"] = self.hostname
1319 self.env_dict["username"] = self.username
1320 self.env_dict["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301321 self.env_dict["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301322 self.env_dict["port_https"] = self.port_https
1323 self.env_dict["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001324
1325 try:
1326 tmp_env_dict = {}
1327 if self.env_vars:
1328 tmp_env_dict = json.loads(self.env_vars)
1329 # Export ENV vars default.
1330 for key, value in tmp_env_dict.items():
1331 os.environ[key] = value
1332 self.env_dict[key] = str(value)
1333
George Keishing0e9b5ba2025-05-08 12:17:58 +05301334 # Load user specified ENV config YAML.
George Keishinge1686752021-07-27 12:55:28 -05001335 if self.econfig:
Patrick Williams20f38712022-12-08 06:18:26 -06001336 with open(self.econfig, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -05001337 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -07001338 tmp_env_dict = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -05001339 except yaml.YAMLError as e:
1340 self.logger.error(e)
1341 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001342 # Export ENV vars.
Patrick Williams20f38712022-12-08 06:18:26 -06001343 for key, value in tmp_env_dict["env_params"].items():
George Keishinge1686752021-07-27 12:55:28 -05001344 os.environ[key] = str(value)
1345 self.env_dict[key] = str(value)
1346 except json.decoder.JSONDecodeError as e:
1347 self.logger.error("\n\tERROR: %s " % e)
1348 sys.exit(-1)
George Keishing0e9b5ba2025-05-08 12:17:58 +05301349 except FileNotFoundError as e:
1350 self.logger.error("\n\tERROR: %s " % e)
1351 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001352
1353 # This to mask the password from displaying on the console.
1354 mask_dict = self.env_dict.copy()
1355 for k, v in mask_dict.items():
1356 if k.lower().find("password") != -1:
1357 hidden_text = []
1358 hidden_text.append(v)
Patrick Williams20f38712022-12-08 06:18:26 -06001359 password_regex = (
1360 "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1361 )
George Keishinge1686752021-07-27 12:55:28 -05001362 mask_dict[k] = re.sub(password_regex, "********", v)
1363
1364 self.logger.info(json.dumps(mask_dict, indent=8, sort_keys=False))
George Keishingb97a9042021-07-29 07:41:20 -05001365
1366 def execute_python_eval(self, eval_string):
1367 r"""
George Keishing04416092025-05-17 18:47:10 +05301368 Execute a qualified Python function string using the eval() function.
George Keishingb97a9042021-07-29 07:41:20 -05001369
George Keishing04416092025-05-17 18:47:10 +05301370 This method executes a provided Python function string using the
1371 eval() function.
1372
1373 The method takes the eval_string as an argument, which is expected to
1374 be a valid Python function call.
1375
1376 The method returns the result of the executed function.
George Keishingb97a9042021-07-29 07:41:20 -05001377
1378 Example:
1379 eval(plugin.foo_func.foo_func(10))
George Keishing04416092025-05-17 18:47:10 +05301380
1381 Parameters:
1382 eval_string (str): A valid Python function call string.
1383
1384 Returns:
1385 str: The result of the executed function and on failure return
1386 PLUGIN_EVAL_ERROR.
George Keishingb97a9042021-07-29 07:41:20 -05001387 """
1388 try:
George Keishingdda48ce2021-08-12 07:02:27 -05001389 self.logger.info("\tExecuting plugin func()")
1390 self.logger.debug("\tCall func: %s" % eval_string)
George Keishingb97a9042021-07-29 07:41:20 -05001391 result = eval(eval_string)
1392 self.logger.info("\treturn: %s" % str(result))
Patrick Williams20f38712022-12-08 06:18:26 -06001393 except (
1394 ValueError,
1395 SyntaxError,
1396 NameError,
1397 AttributeError,
1398 TypeError,
1399 ) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001400 self.logger.error("\tERROR: execute_python_eval: %s" % e)
1401 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001402 plugin_error_dict["exit_on_error"] = True
George Keishing73b95d12021-08-13 14:30:52 -05001403 self.logger.info("\treturn: PLUGIN_EVAL_ERROR")
Patrick Williams20f38712022-12-08 06:18:26 -06001404 return "PLUGIN_EVAL_ERROR"
George Keishingb97a9042021-07-29 07:41:20 -05001405
1406 return result
1407
1408 def execute_plugin_block(self, plugin_cmd_list):
1409 r"""
George Keishing04416092025-05-17 18:47:10 +05301410 Pack the plugin commands into qualified Python string objects.
George Keishingb97a9042021-07-29 07:41:20 -05001411
George Keishing04416092025-05-17 18:47:10 +05301412 This method processes the plugin_cmd_list argument, which is expected
1413 to contain a list of plugin commands read from a YAML file. The method
1414 iterates through the list, constructs a qualified Python string object
1415 for each plugin command, and returns a list of these string objects.
George Keishingb97a9042021-07-29 07:41:20 -05001416
George Keishing04416092025-05-17 18:47:10 +05301417 Parameters:
1418 plugin_cmd_list (list): A list of plugin commands containing
1419 plugin names and arguments.
1420 Plugin block read from YAML
1421 [
1422 {'plugin_name':'plugin.foo_func.my_func'},
1423 {'plugin_args':[10]},
1424 ]
George Keishingb97a9042021-07-29 07:41:20 -05001425
George Keishing04416092025-05-17 18:47:10 +05301426 Example:
1427 Execute and no return response
1428 - plugin:
1429 - plugin_name: plugin.foo_func.my_func
1430 - plugin_args:
1431 - arg1
1432 - arg2
George Keishingb97a9042021-07-29 07:41:20 -05001433
George Keishing04416092025-05-17 18:47:10 +05301434 Execute and return a response
1435 - plugin:
1436 - plugin_name: result = plugin.foo_func.my_func
1437 - plugin_args:
1438 - arg1
1439 - arg2
1440
1441 Execute and return multiple values response
1442 - plugin:
1443 - plugin_name: result1,result2 = plugin.foo_func.my_func
1444 - plugin_args:
1445 - arg1
1446 - arg2
1447
1448 Returns:
1449 str: Execute and not response or a string value(s) responses,
1450
George Keishingb97a9042021-07-29 07:41:20 -05001451 """
1452 try:
Patrick Williams20f38712022-12-08 06:18:26 -06001453 idx = self.key_index_list_dict("plugin_name", plugin_cmd_list)
1454 plugin_name = plugin_cmd_list[idx]["plugin_name"]
George Keishingb97a9042021-07-29 07:41:20 -05001455 # Equal separator means plugin function returns result.
Patrick Williams20f38712022-12-08 06:18:26 -06001456 if " = " in plugin_name:
George Keishingb97a9042021-07-29 07:41:20 -05001457 # Ex. ['result', 'plugin.foo_func.my_func']
Patrick Williams20f38712022-12-08 06:18:26 -06001458 plugin_name_args = plugin_name.split(" = ")
George Keishingb97a9042021-07-29 07:41:20 -05001459 # plugin func return data.
1460 for arg in plugin_name_args:
1461 if arg == plugin_name_args[-1]:
1462 plugin_name = arg
1463 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001464 plugin_resp = arg.split(",")
George Keishingb97a9042021-07-29 07:41:20 -05001465 # ['result1','result2']
1466 for x in plugin_resp:
1467 global_plugin_list.append(x)
1468 global_plugin_dict[x] = ""
1469
1470 # Walk the plugin args ['arg1,'arg2']
1471 # If the YAML plugin statement 'plugin_args' is not declared.
George Keishingf0eb1d62025-05-14 15:07:02 +05301472 plugin_args = []
Patrick Williams20f38712022-12-08 06:18:26 -06001473 if any("plugin_args" in d for d in plugin_cmd_list):
1474 idx = self.key_index_list_dict("plugin_args", plugin_cmd_list)
George Keishingf0eb1d62025-05-14 15:07:02 +05301475 if idx is not None:
1476 plugin_args = plugin_cmd_list[idx].get("plugin_args", [])
George Keishingb97a9042021-07-29 07:41:20 -05001477 plugin_args = self.yaml_args_populate(plugin_args)
1478 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301479 plugin_args = self.yaml_args_populate([])
George Keishingb97a9042021-07-29 07:41:20 -05001480
George Keishing450f92f2025-05-15 23:12:51 +05301481 # Pack the args list to string parameters for plugin function.
George Keishingb97a9042021-07-29 07:41:20 -05001482 parm_args_str = self.yaml_args_string(plugin_args)
George Keishing450f92f2025-05-15 23:12:51 +05301483
1484 """
1485 Example of plugin_func:
1486 plugin.redfish.enumerate_request(
1487 "xx.xx.xx.xx:443",
1488 "root",
1489 "********",
1490 "/redfish/v1/",
1491 "json")
1492 """
George Keishingb97a9042021-07-29 07:41:20 -05001493 if parm_args_str:
George Keishing450f92f2025-05-15 23:12:51 +05301494 plugin_func = f"{plugin_name}({parm_args_str})"
George Keishingb97a9042021-07-29 07:41:20 -05001495 else:
George Keishing450f92f2025-05-15 23:12:51 +05301496 plugin_func = f"{plugin_name}()"
George Keishingb97a9042021-07-29 07:41:20 -05001497
1498 # Execute plugin function.
1499 if global_plugin_dict:
1500 resp = self.execute_python_eval(plugin_func)
George Keishing9348b402021-08-13 12:22:35 -05001501 # Update plugin vars dict if there is any.
Patrick Williams20f38712022-12-08 06:18:26 -06001502 if resp != "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001503 self.response_args_data(resp)
George Keishingb97a9042021-07-29 07:41:20 -05001504 else:
George Keishingcaa97e62021-08-03 14:00:09 -05001505 resp = self.execute_python_eval(plugin_func)
George Keishingb97a9042021-07-29 07:41:20 -05001506 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001507 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001508 plugin_error_dict["exit_on_error"] = True
George Keishing1e7b0182021-08-06 14:05:54 -05001509 self.logger.error("\tERROR: execute_plugin_block: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001510 pass
1511
George Keishing73b95d12021-08-13 14:30:52 -05001512 # There is a real error executing the plugin function.
Patrick Williams20f38712022-12-08 06:18:26 -06001513 if resp == "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001514 return resp
1515
George Keishingde79a9b2021-08-12 16:14:43 -05001516 # Check if plugin_expects_return (int, string, list,dict etc)
Patrick Williams20f38712022-12-08 06:18:26 -06001517 if any("plugin_expects_return" in d for d in plugin_cmd_list):
1518 idx = self.key_index_list_dict(
1519 "plugin_expects_return", plugin_cmd_list
1520 )
1521 plugin_expects = plugin_cmd_list[idx]["plugin_expects_return"]
George Keishingde79a9b2021-08-12 16:14:43 -05001522 if plugin_expects:
1523 if resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001524 if (
1525 self.plugin_expect_type(plugin_expects, resp)
1526 == "INVALID"
1527 ):
George Keishingde79a9b2021-08-12 16:14:43 -05001528 self.logger.error("\tWARN: Plugin error check skipped")
1529 elif not self.plugin_expect_type(plugin_expects, resp):
Patrick Williams20f38712022-12-08 06:18:26 -06001530 self.logger.error(
1531 "\tERROR: Plugin expects return data: %s"
1532 % plugin_expects
1533 )
1534 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001535 elif not resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001536 self.logger.error(
1537 "\tERROR: Plugin func failed to return data"
1538 )
1539 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001540
1541 return resp
1542
George Keishingb97a9042021-07-29 07:41:20 -05001543 def response_args_data(self, plugin_resp):
1544 r"""
George Keishing9348b402021-08-13 12:22:35 -05001545 Parse the plugin function response and update plugin return variable.
George Keishingb97a9042021-07-29 07:41:20 -05001546
1547 plugin_resp Response data from plugin function.
1548 """
1549 resp_list = []
George Keishing5765f792021-08-02 13:08:53 -05001550 resp_data = ""
George Keishing9348b402021-08-13 12:22:35 -05001551
George Keishingb97a9042021-07-29 07:41:20 -05001552 # There is nothing to update the plugin response.
Patrick Williams20f38712022-12-08 06:18:26 -06001553 if len(global_plugin_list) == 0 or plugin_resp == "None":
George Keishingb97a9042021-07-29 07:41:20 -05001554 return
1555
George Keishing5765f792021-08-02 13:08:53 -05001556 if isinstance(plugin_resp, str):
Patrick Williams20f38712022-12-08 06:18:26 -06001557 resp_data = plugin_resp.strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001558 resp_list.append(resp_data)
1559 elif isinstance(plugin_resp, bytes):
Patrick Williams20f38712022-12-08 06:18:26 -06001560 resp_data = str(plugin_resp, "UTF-8").strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001561 resp_list.append(resp_data)
1562 elif isinstance(plugin_resp, tuple):
1563 if len(global_plugin_list) == 1:
George Keishingb97a9042021-07-29 07:41:20 -05001564 resp_list.append(plugin_resp)
George Keishing5765f792021-08-02 13:08:53 -05001565 else:
1566 resp_list = list(plugin_resp)
Patrick Williams20f38712022-12-08 06:18:26 -06001567 resp_list = [x.strip("\r\n\t") for x in resp_list]
George Keishingb97a9042021-07-29 07:41:20 -05001568 elif isinstance(plugin_resp, list):
George Keishing5765f792021-08-02 13:08:53 -05001569 if len(global_plugin_list) == 1:
Patrick Williams20f38712022-12-08 06:18:26 -06001570 resp_list.append([x.strip("\r\n\t") for x in plugin_resp])
George Keishing5765f792021-08-02 13:08:53 -05001571 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001572 resp_list = [x.strip("\r\n\t") for x in plugin_resp]
George Keishing5765f792021-08-02 13:08:53 -05001573 elif isinstance(plugin_resp, int) or isinstance(plugin_resp, float):
1574 resp_list.append(plugin_resp)
George Keishingb97a9042021-07-29 07:41:20 -05001575
George Keishing9348b402021-08-13 12:22:35 -05001576 # Iterate if there is a list of plugin return vars to update.
George Keishingb97a9042021-07-29 07:41:20 -05001577 for idx, item in enumerate(resp_list, start=0):
George Keishing9348b402021-08-13 12:22:35 -05001578 # Exit loop, done required loop.
George Keishingb97a9042021-07-29 07:41:20 -05001579 if idx >= len(global_plugin_list):
1580 break
1581 # Find the index of the return func in the list and
1582 # update the global func return dictionary.
1583 try:
1584 dict_idx = global_plugin_list[idx]
1585 global_plugin_dict[dict_idx] = item
1586 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001587 self.logger.warn("\tWARN: response_args_data: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001588 pass
1589
1590 # Done updating plugin dict irrespective of pass or failed,
George Keishing9348b402021-08-13 12:22:35 -05001591 # clear all the list element for next plugin block execute.
George Keishingb97a9042021-07-29 07:41:20 -05001592 global_plugin_list.clear()
1593
1594 def yaml_args_string(self, plugin_args):
1595 r"""
George Keishing450f92f2025-05-15 23:12:51 +05301596 Pack the arguments into a string representation.
George Keishingb97a9042021-07-29 07:41:20 -05001597
George Keishing450f92f2025-05-15 23:12:51 +05301598 This method processes the plugin_arg argument, which is expected to
1599 contain a list of arguments. The method iterates through the list,
1600 converts each argument to a string, and concatenates them into a
1601 single string. Special handling is applied for integer, float, and
1602 predefined plugin variable types.
1603
1604 Ecample:
1605 From
1606 ['xx.xx.xx.xx:443', 'root', '********', '/redfish/v1/', 'json']
1607 to
1608 "xx.xx.xx.xx:443","root","********","/redfish/v1/","json"
1609
1610 Parameters:
1611 plugin_args (list): A list of arguments to be packed into
1612 a string.
1613
1614 Returns:
1615 str: A string representation of the arguments.
George Keishingb97a9042021-07-29 07:41:20 -05001616 """
Patrick Williams20f38712022-12-08 06:18:26 -06001617 args_str = ""
George Keishing450f92f2025-05-15 23:12:51 +05301618
1619 for i, arg in enumerate(plugin_args):
1620 if arg:
1621 if isinstance(arg, (int, float)):
1622 args_str += str(arg)
1623 elif arg in global_plugin_type_list:
1624 args_str += str(global_plugin_dict[arg])
George Keishingb97a9042021-07-29 07:41:20 -05001625 else:
George Keishing450f92f2025-05-15 23:12:51 +05301626 args_str += f'"{arg.strip("\r\n\t")}"'
1627
1628 # Skip last list element.
1629 if i != len(plugin_args) - 1:
1630 args_str += ","
1631
George Keishingb97a9042021-07-29 07:41:20 -05001632 return args_str
1633
1634 def yaml_args_populate(self, yaml_arg_list):
1635 r"""
George Keishingf0eb1d62025-05-14 15:07:02 +05301636 Decode environment and plugin variables and populate the argument list.
George Keishingb97a9042021-07-29 07:41:20 -05001637
George Keishingf0eb1d62025-05-14 15:07:02 +05301638 This method processes the yaml_arg_list argument, which is expected to
1639 contain a list of arguments read from a YAML file. The method iterates
1640 through the list, decodes environment and plugin variables, and
1641 returns a populated list of arguments.
George Keishingb97a9042021-07-29 07:41:20 -05001642
George Keishingf0eb1d62025-05-14 15:07:02 +05301643 .. code-block:: yaml
1644
George Keishingb97a9042021-07-29 07:41:20 -05001645 - plugin_args:
1646 - arg1
1647 - arg2
1648
George Keishingf0eb1d62025-05-14 15:07:02 +05301649 ['${hostname}:${port_https}', '${username}', '/redfish/v1/', 'json']
George Keishingb97a9042021-07-29 07:41:20 -05001650
George Keishingf0eb1d62025-05-14 15:07:02 +05301651 Returns the populated plugin list
1652 ['xx.xx.xx.xx:443', 'root', '/redfish/v1/', 'json']
1653
1654 Parameters:
1655 yaml_arg_list (list): A list of arguments containing environment
1656 and plugin variables.
1657
1658 Returns:
1659 list: A populated list of arguments with decoded environment and
1660 plugin variables.
1661 """
George Keishingb97a9042021-07-29 07:41:20 -05001662 if isinstance(yaml_arg_list, list):
George Keishingf0eb1d62025-05-14 15:07:02 +05301663 populated_list = []
George Keishingb97a9042021-07-29 07:41:20 -05001664 for arg in yaml_arg_list:
George Keishing0581cb02021-08-05 15:08:58 -05001665 if isinstance(arg, (int, float)):
George Keishingf0eb1d62025-05-14 15:07:02 +05301666 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001667 elif isinstance(arg, str):
1668 arg_str = self.yaml_env_and_plugin_vars_populate(str(arg))
George Keishingf0eb1d62025-05-14 15:07:02 +05301669 populated_list.append(arg_str)
George Keishingb97a9042021-07-29 07:41:20 -05001670 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301671 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001672
George Keishingf0eb1d62025-05-14 15:07:02 +05301673 return populated_list
George Keishingb97a9042021-07-29 07:41:20 -05001674
1675 def yaml_env_and_plugin_vars_populate(self, yaml_arg_str):
1676 r"""
George Keishinga593f4b2025-05-13 20:02:36 +05301677 Update environment variables and plugin variables based on the
1678 provided YAML argument string.
George Keishingb97a9042021-07-29 07:41:20 -05001679
George Keishinga593f4b2025-05-13 20:02:36 +05301680 This method processes the yaml_arg_str argument, which is expected
1681 to contain a string representing environment variables and plugin
1682 variables in the format:
George Keishingb97a9042021-07-29 07:41:20 -05001683
George Keishinga593f4b2025-05-13 20:02:36 +05301684 .. code-block:: yaml
1685
George Keishingb97a9042021-07-29 07:41:20 -05001686 - cat ${MY_VAR}
1687 - ls -AX my_plugin_var
George Keishinga593f4b2025-05-13 20:02:36 +05301688
1689 The method parses the string, extracts the variable names, and updates
1690 the corresponding environment variables and plugin variables.
1691
1692 Parameters:
1693 yaml_arg_str (str): A string containing environment and plugin
1694 variable definitions in YAML format.
1695
1696 Returns:
1697 str: The updated YAML argument string with plugin variables
1698 replaced.
George Keishingb97a9042021-07-29 07:41:20 -05001699 """
George Keishinga593f4b2025-05-13 20:02:36 +05301700
1701 # Parse and convert the Plugin YAML vars string to python vars
1702 # Example:
1703 # ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
George Keishingb97a9042021-07-29 07:41:20 -05001704 try:
George Keishingc754b432025-04-24 14:27:14 +05301705 # Example, list of matching
1706 # env vars ['username', 'password', 'hostname']
George Keishingb97a9042021-07-29 07:41:20 -05001707 # Extra escape \ for special symbols. '\$\{([^\}]+)\}' works good.
George Keishinga593f4b2025-05-13 20:02:36 +05301708 env_var_regex = r"\$\{([^\}]+)\}"
1709 env_var_names_list = re.findall(env_var_regex, yaml_arg_str)
1710
George Keishingb97a9042021-07-29 07:41:20 -05001711 for var in env_var_names_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301712 env_var = os.environ.get(var)
1713 if env_var:
1714 env_replace = "${" + var + "}"
1715 yaml_arg_str = yaml_arg_str.replace(env_replace, env_var)
George Keishingb97a9042021-07-29 07:41:20 -05001716 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001717 self.logger.error("\tERROR:yaml_env_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001718 pass
1719
George Keishinga593f4b2025-05-13 20:02:36 +05301720 """
1721 Parse the string for plugin vars.
1722 Implement the logic to update environment variables based on the
1723 extracted variable names.
1724 """
George Keishingb97a9042021-07-29 07:41:20 -05001725 try:
George Keishinga593f4b2025-05-13 20:02:36 +05301726 # Example, list of plugin vars env_var_names_list
1727 # ['my_hostname', 'port_https']
1728 global_plugin_dict_keys = set(global_plugin_dict.keys())
1729 # Skip env var list already populated above code block list.
1730 plugin_var_name_list = [
1731 var
1732 for var in global_plugin_dict_keys
1733 if var not in env_var_names_list
1734 ]
1735
George Keishingb97a9042021-07-29 07:41:20 -05001736 for var in plugin_var_name_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301737 plugin_var_value = global_plugin_dict[var]
George Keishing0581cb02021-08-05 15:08:58 -05001738 if yaml_arg_str in global_plugin_dict:
George Keishinga593f4b2025-05-13 20:02:36 +05301739 """
1740 If this plugin var exist but empty in dict, don't replace.
1741 his is either a YAML plugin statement incorrectly used or
1742 user added a plugin var which is not going to be populated.
1743 """
1744 if isinstance(plugin_var_value, (list, dict)):
1745 """
1746 List data type or dict can't be replaced, use
1747 directly in eval function call.
1748 """
George Keishing0581cb02021-08-05 15:08:58 -05001749 global_plugin_type_list.append(var)
1750 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001751 yaml_arg_str = yaml_arg_str.replace(
George Keishinga593f4b2025-05-13 20:02:36 +05301752 str(var), str(plugin_var_value)
Patrick Williams20f38712022-12-08 06:18:26 -06001753 )
George Keishingb97a9042021-07-29 07:41:20 -05001754 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001755 self.logger.error("\tERROR: yaml_plugin_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001756 pass
1757
George Keishinga593f4b2025-05-13 20:02:36 +05301758 # From ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
1759 # to populated values string as
1760 # Example: xx.xx.xx.xx:443 and return the string
George Keishingb97a9042021-07-29 07:41:20 -05001761 return yaml_arg_str
George Keishing1e7b0182021-08-06 14:05:54 -05001762
1763 def plugin_error_check(self, plugin_dict):
1764 r"""
George Keishing1e877422025-05-09 20:45:09 +05301765 Process plugin error dictionary and return the corresponding error
1766 message.
George Keishing1e7b0182021-08-06 14:05:54 -05001767
George Keishing1e877422025-05-09 20:45:09 +05301768 This method checks if any dictionary in the plugin_dict list contains
1769 a "plugin_error" key. If such a dictionary is found, it retrieves the
1770 value associated with the "plugin_error" key and returns the
1771 corresponding error message from the plugin_error_dict attribute.
1772
1773 Parameters:
1774 plugin_dict (list of dict): A list of dictionaries containing
1775 plugin error information.
1776
1777 Returns:
1778 str: The error message corresponding to the "plugin_error" value,
1779 or None if no error is found.
George Keishing1e7b0182021-08-06 14:05:54 -05001780 """
Patrick Williams20f38712022-12-08 06:18:26 -06001781 if any("plugin_error" in d for d in plugin_dict):
George Keishing1e7b0182021-08-06 14:05:54 -05001782 for d in plugin_dict:
Patrick Williams20f38712022-12-08 06:18:26 -06001783 if "plugin_error" in d:
1784 value = d["plugin_error"]
George Keishing1e877422025-05-09 20:45:09 +05301785 return self.plugin_error_dict.get(value, None)
1786 return None
George Keishingde79a9b2021-08-12 16:14:43 -05001787
1788 def key_index_list_dict(self, key, list_dict):
1789 r"""
George Keishing1e877422025-05-09 20:45:09 +05301790 Find the index of the first dictionary in the list that contains
1791 the specified key.
George Keishingde79a9b2021-08-12 16:14:43 -05001792
George Keishing1e877422025-05-09 20:45:09 +05301793 Parameters:
1794 key (str): The key to search for in the
1795 dictionaries.
1796 list_dict (list of dict): A list of dictionaries to search
1797 through.
1798
1799 Returns:
1800 int: The index of the first dictionary containing the key, or -1
1801 if no match is found.
George Keishingde79a9b2021-08-12 16:14:43 -05001802 """
1803 for i, d in enumerate(list_dict):
George Keishing1e877422025-05-09 20:45:09 +05301804 if key in d:
George Keishingde79a9b2021-08-12 16:14:43 -05001805 return i
George Keishing1e877422025-05-09 20:45:09 +05301806 return -1
George Keishingde79a9b2021-08-12 16:14:43 -05001807
1808 def plugin_expect_type(self, type, data):
1809 r"""
George Keishing1e877422025-05-09 20:45:09 +05301810 Check if the provided data matches the expected type.
1811
1812 This method checks if the data argument matches the specified type.
1813 It supports the following types: "int", "float", "str", "list", "dict",
1814 and "tuple".
1815
1816 If the type is not recognized, it logs an info message and returns
1817 "INVALID".
1818
1819 Parameters:
1820 type (str): The expected data type.
1821 data: The data to check against the expected type.
1822
1823 Returns:
1824 bool or str: True if the data matches the expected type, False if
1825 not, or "INVALID" if the type is not recognized.
George Keishingde79a9b2021-08-12 16:14:43 -05001826 """
Patrick Williams20f38712022-12-08 06:18:26 -06001827 if type == "int":
George Keishingde79a9b2021-08-12 16:14:43 -05001828 return isinstance(data, int)
Patrick Williams20f38712022-12-08 06:18:26 -06001829 elif type == "float":
George Keishingde79a9b2021-08-12 16:14:43 -05001830 return isinstance(data, float)
Patrick Williams20f38712022-12-08 06:18:26 -06001831 elif type == "str":
George Keishingde79a9b2021-08-12 16:14:43 -05001832 return isinstance(data, str)
Patrick Williams20f38712022-12-08 06:18:26 -06001833 elif type == "list":
George Keishingde79a9b2021-08-12 16:14:43 -05001834 return isinstance(data, list)
Patrick Williams20f38712022-12-08 06:18:26 -06001835 elif type == "dict":
George Keishingde79a9b2021-08-12 16:14:43 -05001836 return isinstance(data, dict)
Patrick Williams20f38712022-12-08 06:18:26 -06001837 elif type == "tuple":
George Keishingde79a9b2021-08-12 16:14:43 -05001838 return isinstance(data, tuple)
1839 else:
1840 self.logger.info("\tInvalid data type requested: %s" % type)
Patrick Williams20f38712022-12-08 06:18:26 -06001841 return "INVALID"