blob: 2d0c687f8193e32d25522e59646a0454fc082095 [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"""
414 Open a ssh connection to targeted system.
415
416 """
417
Patrick Williams20f38712022-12-08 06:18:26 -0600418 self.ssh_remoteclient = SSHRemoteclient(
George Keishing7a61aa22023-06-26 13:18:37 +0530419 self.hostname, self.username, self.password, self.port_ssh
Patrick Williams20f38712022-12-08 06:18:26 -0600420 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500421
Peter D Phan5963d632021-07-12 09:58:55 -0500422 if self.ssh_remoteclient.ssh_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600423 self.logger.info(
424 "\n\t[Check] %s SSH connection established.\t [OK]"
425 % self.hostname
426 )
Peter D Phan733df632021-06-17 13:13:36 -0500427
Peter D Phan5963d632021-07-12 09:58:55 -0500428 # Check scp connection.
429 # If scp connection fails,
430 # continue with FFDC generation but skip scp files to local host.
431 self.ssh_remoteclient.scp_connection()
432 return True
433 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600434 self.logger.info(
435 "\n\t[Check] %s SSH connection.\t [NOT AVAILABLE]"
436 % self.hostname
437 )
Peter D Phan5963d632021-07-12 09:58:55 -0500438 return False
439
440 def telnet_to_target_system(self):
441 r"""
442 Open a telnet connection to targeted system.
443 """
Patrick Williams20f38712022-12-08 06:18:26 -0600444 self.telnet_remoteclient = TelnetRemoteclient(
445 self.hostname, self.username, self.password
446 )
Peter D Phan5963d632021-07-12 09:58:55 -0500447 if self.telnet_remoteclient.tn_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600448 self.logger.info(
449 "\n\t[Check] %s Telnet connection established.\t [OK]"
450 % self.hostname
451 )
Peter D Phan5963d632021-07-12 09:58:55 -0500452 return True
453 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600454 self.logger.info(
455 "\n\t[Check] %s Telnet connection.\t [NOT AVAILABLE]"
456 % self.hostname
457 )
Peter D Phan5963d632021-07-12 09:58:55 -0500458 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500459
George Keishing772c9772021-06-16 23:23:42 -0500460 def generate_ffdc(self, working_protocol_list):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500461 r"""
Peter D Phan04aca3b2021-06-21 10:37:18 -0500462 Determine actions based on remote host type
Peter D Phan72ce6b82021-06-03 06:18:26 -0500463
Peter D Phan04aca3b2021-06-21 10:37:18 -0500464 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530465 working_protocol_list List of confirmed working protocols to
466 connect to remote host.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500467 """
468
Patrick Williams20f38712022-12-08 06:18:26 -0600469 self.logger.info(
470 "\n\t---- Executing commands on " + self.hostname + " ----"
471 )
472 self.logger.info(
473 "\n\tWorking protocol list: %s" % working_protocol_list
474 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500475
George Keishingf5a57502021-07-22 16:43:47 -0500476 config_dict = self.ffdc_actions
477 for target_type in config_dict.keys():
478 if self.target_type != target_type:
George Keishing6ea92b02021-07-01 11:20:50 -0500479 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500480
Peter D Phane86d9a52021-07-15 10:42:25 -0500481 self.logger.info("\n\tFFDC Path: %s " % self.ffdc_dir_path)
Patrick Williams20f38712022-12-08 06:18:26 -0600482 global_plugin_dict["global_log_store_path"] = self.ffdc_dir_path
George Keishingf5a57502021-07-22 16:43:47 -0500483 self.logger.info("\tSystem Type: %s" % target_type)
484 for k, v in config_dict[target_type].items():
Patrick Williams20f38712022-12-08 06:18:26 -0600485 if (
486 self.remote_protocol not in working_protocol_list
487 and self.remote_protocol != "ALL"
488 ):
George Keishing6ea92b02021-07-01 11:20:50 -0500489 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500490
Patrick Williams20f38712022-12-08 06:18:26 -0600491 protocol = config_dict[target_type][k]["PROTOCOL"][0]
George Keishingf5a57502021-07-22 16:43:47 -0500492
493 if protocol not in working_protocol_list:
494 continue
495
George Keishingb7607612021-07-27 13:31:23 -0500496 if protocol in working_protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -0600497 if protocol == "SSH" or protocol == "SCP":
George Keishing12fd0652021-07-27 13:57:11 -0500498 self.protocol_ssh(protocol, target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600499 elif protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -0500500 self.protocol_telnet(target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600501 elif (
502 protocol == "REDFISH"
503 or protocol == "IPMI"
504 or protocol == "SHELL"
505 ):
George Keishing506b0582021-07-27 09:31:22 -0500506 self.protocol_execute(protocol, target_type, k)
George Keishingb7607612021-07-27 13:31:23 -0500507 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600508 self.logger.error(
509 "\n\tERROR: %s is not available for %s."
510 % (protocol, self.hostname)
511 )
George Keishingeafba182021-06-29 13:44:58 -0500512
Peter D Phan04aca3b2021-06-21 10:37:18 -0500513 # Close network connection after collecting all files
Patrick Williams20f38712022-12-08 06:18:26 -0600514 self.elapsed_time = time.strftime(
515 "%H:%M:%S", time.gmtime(time.time() - self.start_time)
516 )
George Keishing48972ba2025-05-05 17:40:29 +0530517 self.logger.info("\n\tTotal time taken: %s" % self.elapsed_time)
Peter D Phanbff617a2021-07-22 08:41:35 -0500518 if self.ssh_remoteclient:
519 self.ssh_remoteclient.ssh_remoteclient_disconnect()
520 if self.telnet_remoteclient:
521 self.telnet_remoteclient.tn_remoteclient_disconnect()
Peter D Phan04aca3b2021-06-21 10:37:18 -0500522
Patrick Williams20f38712022-12-08 06:18:26 -0600523 def protocol_ssh(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500524 r"""
525 Perform actions using SSH and SCP protocols.
526
527 Description of argument(s):
George Keishing12fd0652021-07-27 13:57:11 -0500528 protocol Protocol to execute.
George Keishingf5a57502021-07-22 16:43:47 -0500529 target_type OS Type of remote host.
George Keishing6ea92b02021-07-01 11:20:50 -0500530 sub_type Group type of commands.
Peter D Phan0c669772021-06-24 13:52:42 -0500531 """
532
Patrick Williams20f38712022-12-08 06:18:26 -0600533 if protocol == "SCP":
George Keishingf5a57502021-07-22 16:43:47 -0500534 self.group_copy(self.ffdc_actions[target_type][sub_type])
George Keishing6ea92b02021-07-01 11:20:50 -0500535 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600536 self.collect_and_copy_ffdc(
537 self.ffdc_actions[target_type][sub_type]
538 )
Peter D Phan0c669772021-06-24 13:52:42 -0500539
Patrick Williams20f38712022-12-08 06:18:26 -0600540 def protocol_telnet(self, target_type, sub_type):
Peter D Phan5963d632021-07-12 09:58:55 -0500541 r"""
542 Perform actions using telnet protocol.
543 Description of argument(s):
George Keishingf5a57502021-07-22 16:43:47 -0500544 target_type OS Type of remote host.
Peter D Phan5963d632021-07-12 09:58:55 -0500545 """
Patrick Williams20f38712022-12-08 06:18:26 -0600546 self.logger.info(
547 "\n\t[Run] Executing commands on %s using %s"
548 % (self.hostname, "TELNET")
549 )
Peter D Phan5963d632021-07-12 09:58:55 -0500550 telnet_files_saved = []
551 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600552 list_of_commands = self.ffdc_actions[target_type][sub_type]["COMMANDS"]
Peter D Phan5963d632021-07-12 09:58:55 -0500553 for index, each_cmd in enumerate(list_of_commands, start=0):
554 command_txt, command_timeout = self.unpack_command(each_cmd)
Patrick Williams20f38712022-12-08 06:18:26 -0600555 result = self.telnet_remoteclient.execute_command(
556 command_txt, command_timeout
557 )
Peter D Phan5963d632021-07-12 09:58:55 -0500558 if result:
559 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600560 targ_file = self.ffdc_actions[target_type][sub_type][
561 "FILES"
562 ][index]
Peter D Phan5963d632021-07-12 09:58:55 -0500563 except IndexError:
Peter D Phane86d9a52021-07-15 10:42:25 -0500564 targ_file = command_txt
565 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600566 "\n\t[WARN] Missing filename to store data from"
567 " telnet %s." % each_cmd
568 )
569 self.logger.warning(
570 "\t[WARN] Data will be stored in %s." % targ_file
571 )
572 targ_file_with_path = (
573 self.ffdc_dir_path + self.ffdc_prefix + targ_file
574 )
Peter D Phan5963d632021-07-12 09:58:55 -0500575 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600576 with open(targ_file_with_path, "w") as fp:
Peter D Phan5963d632021-07-12 09:58:55 -0500577 fp.write(result)
578 fp.close
579 telnet_files_saved.append(targ_file)
580 progress_counter += 1
581 self.print_progress(progress_counter)
Peter D Phane86d9a52021-07-15 10:42:25 -0500582 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan5963d632021-07-12 09:58:55 -0500583 for file in telnet_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500584 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
Peter D Phan5963d632021-07-12 09:58:55 -0500585
Patrick Williams20f38712022-12-08 06:18:26 -0600586 def protocol_execute(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500587 r"""
George Keishing506b0582021-07-27 09:31:22 -0500588 Perform actions for a given protocol.
Peter D Phan0c669772021-06-24 13:52:42 -0500589
590 Description of argument(s):
George Keishing506b0582021-07-27 09:31:22 -0500591 protocol Protocol to execute.
George Keishingf5a57502021-07-22 16:43:47 -0500592 target_type OS Type of remote host.
George Keishing6ea92b02021-07-01 11:20:50 -0500593 sub_type Group type of commands.
Peter D Phan0c669772021-06-24 13:52:42 -0500594 """
595
Patrick Williams20f38712022-12-08 06:18:26 -0600596 self.logger.info(
597 "\n\t[Run] Executing commands to %s using %s"
598 % (self.hostname, protocol)
599 )
George Keishing506b0582021-07-27 09:31:22 -0500600 executed_files_saved = []
George Keishingeafba182021-06-29 13:44:58 -0500601 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600602 list_of_cmd = self.get_command_list(
603 self.ffdc_actions[target_type][sub_type]
604 )
George Keishingeafba182021-06-29 13:44:58 -0500605 for index, each_cmd in enumerate(list_of_cmd, start=0):
George Keishingcaa97e62021-08-03 14:00:09 -0500606 plugin_call = False
George Keishingb97a9042021-07-29 07:41:20 -0500607 if isinstance(each_cmd, dict):
Patrick Williams20f38712022-12-08 06:18:26 -0600608 if "plugin" in each_cmd:
George Keishing1e7b0182021-08-06 14:05:54 -0500609 # If the error is set and plugin explicitly
610 # requested to skip execution on error..
Patrick Williams20f38712022-12-08 06:18:26 -0600611 if plugin_error_dict[
612 "exit_on_error"
613 ] and self.plugin_error_check(each_cmd["plugin"]):
614 self.logger.info(
615 "\n\t[PLUGIN-ERROR] exit_on_error: %s"
616 % plugin_error_dict["exit_on_error"]
617 )
618 self.logger.info(
619 "\t[PLUGIN-SKIP] %s" % each_cmd["plugin"][0]
620 )
George Keishing1e7b0182021-08-06 14:05:54 -0500621 continue
George Keishingcaa97e62021-08-03 14:00:09 -0500622 plugin_call = True
George Keishingb97a9042021-07-29 07:41:20 -0500623 # call the plugin
624 self.logger.info("\n\t[PLUGIN-START]")
Patrick Williams20f38712022-12-08 06:18:26 -0600625 result = self.execute_plugin_block(each_cmd["plugin"])
George Keishingb97a9042021-07-29 07:41:20 -0500626 self.logger.info("\t[PLUGIN-END]\n")
George Keishingb97a9042021-07-29 07:41:20 -0500627 else:
George Keishing2b83e042021-08-03 12:56:11 -0500628 each_cmd = self.yaml_env_and_plugin_vars_populate(each_cmd)
George Keishingb97a9042021-07-29 07:41:20 -0500629
George Keishingcaa97e62021-08-03 14:00:09 -0500630 if not plugin_call:
631 result = self.run_tool_cmd(each_cmd)
George Keishingeafba182021-06-29 13:44:58 -0500632 if result:
633 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600634 file_name = self.get_file_list(
635 self.ffdc_actions[target_type][sub_type]
636 )[index]
George Keishingb97a9042021-07-29 07:41:20 -0500637 # If file is specified as None.
George Keishing0581cb02021-08-05 15:08:58 -0500638 if file_name == "None":
George Keishingb97a9042021-07-29 07:41:20 -0500639 continue
Patrick Williams20f38712022-12-08 06:18:26 -0600640 targ_file = self.yaml_env_and_plugin_vars_populate(
641 file_name
642 )
George Keishingeafba182021-06-29 13:44:58 -0500643 except IndexError:
Patrick Williams20f38712022-12-08 06:18:26 -0600644 targ_file = each_cmd.split("/")[-1]
George Keishing506b0582021-07-27 09:31:22 -0500645 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600646 "\n\t[WARN] Missing filename to store data from %s."
647 % each_cmd
648 )
649 self.logger.warning(
650 "\t[WARN] Data will be stored in %s." % targ_file
651 )
George Keishingeafba182021-06-29 13:44:58 -0500652
Patrick Williams20f38712022-12-08 06:18:26 -0600653 targ_file_with_path = (
654 self.ffdc_dir_path + self.ffdc_prefix + targ_file
655 )
George Keishingeafba182021-06-29 13:44:58 -0500656
657 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600658 with open(targ_file_with_path, "w") as fp:
George Keishing91308ea2021-08-10 14:43:15 -0500659 if isinstance(result, dict):
660 fp.write(json.dumps(result))
661 else:
662 fp.write(result)
George Keishingeafba182021-06-29 13:44:58 -0500663 fp.close
George Keishing506b0582021-07-27 09:31:22 -0500664 executed_files_saved.append(targ_file)
George Keishingeafba182021-06-29 13:44:58 -0500665
666 progress_counter += 1
667 self.print_progress(progress_counter)
668
Peter D Phane86d9a52021-07-15 10:42:25 -0500669 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
George Keishingeafba182021-06-29 13:44:58 -0500670
George Keishing506b0582021-07-27 09:31:22 -0500671 for file in executed_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500672 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
George Keishingeafba182021-06-29 13:44:58 -0500673
Patrick Williams20f38712022-12-08 06:18:26 -0600674 def collect_and_copy_ffdc(
675 self, ffdc_actions_for_target_type, form_filename=False
676 ):
Peter D Phan04aca3b2021-06-21 10:37:18 -0500677 r"""
678 Send commands in ffdc_config file to targeted system.
679
680 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530681 ffdc_actions_for_target_type Commands and files for the selected
682 remote host type.
683 form_filename If true, pre-pend self.target_type to
684 filename
Peter D Phan04aca3b2021-06-21 10:37:18 -0500685 """
686
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500687 # Executing commands, if any
Patrick Williams20f38712022-12-08 06:18:26 -0600688 self.ssh_execute_ffdc_commands(
689 ffdc_actions_for_target_type, form_filename
690 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500691
Peter D Phan3beb02e2021-07-06 13:25:17 -0500692 # Copying files
Peter D Phan5963d632021-07-12 09:58:55 -0500693 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600694 self.logger.info(
695 "\n\n\tCopying FFDC files from remote system %s.\n"
696 % self.hostname
697 )
Peter D Phan2b8052d2021-06-22 10:55:41 -0500698
Peter D Phan04aca3b2021-06-21 10:37:18 -0500699 # Retrieving files from target system
George Keishingf5a57502021-07-22 16:43:47 -0500700 list_of_files = self.get_file_list(ffdc_actions_for_target_type)
Patrick Williams20f38712022-12-08 06:18:26 -0600701 self.scp_ffdc(
702 self.ffdc_dir_path,
703 self.ffdc_prefix,
704 form_filename,
705 list_of_files,
706 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500707 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600708 self.logger.info(
709 "\n\n\tSkip copying FFDC files from remote system %s.\n"
710 % self.hostname
711 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500712
Patrick Williams20f38712022-12-08 06:18:26 -0600713 def get_command_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500714 r"""
715 Fetch list of commands from configuration file
716
717 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530718 ffdc_actions_for_target_type Commands and files for the selected
719 remote host type.
Peter D Phanbabf2962021-07-07 11:24:40 -0500720 """
721 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600722 list_of_commands = ffdc_actions_for_target_type["COMMANDS"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500723 except KeyError:
724 list_of_commands = []
725 return list_of_commands
726
Patrick Williams20f38712022-12-08 06:18:26 -0600727 def get_file_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500728 r"""
729 Fetch list of commands from configuration file
730
731 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530732 ffdc_actions_for_target_type Commands and files for the selected
733 remote host type.
Peter D Phanbabf2962021-07-07 11:24:40 -0500734 """
735 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600736 list_of_files = ffdc_actions_for_target_type["FILES"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500737 except KeyError:
738 list_of_files = []
739 return list_of_files
740
Patrick Williams20f38712022-12-08 06:18:26 -0600741 def unpack_command(self, command):
Peter D Phan5963d632021-07-12 09:58:55 -0500742 r"""
743 Unpack command from config file
744
745 Description of argument(s):
746 command Command from config file.
747 """
748 if isinstance(command, dict):
749 command_txt = next(iter(command))
750 command_timeout = next(iter(command.values()))
751 elif isinstance(command, str):
752 command_txt = command
753 # Default command timeout 60 seconds
754 command_timeout = 60
755
756 return command_txt, command_timeout
757
Patrick Williams20f38712022-12-08 06:18:26 -0600758 def ssh_execute_ffdc_commands(
759 self, ffdc_actions_for_target_type, form_filename=False
760 ):
Peter D Phan3beb02e2021-07-06 13:25:17 -0500761 r"""
762 Send commands in ffdc_config file to targeted system.
763
764 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530765 ffdc_actions_for_target_type Commands and files for the selected
766 remote host type.
767 form_filename If true, pre-pend self.target_type to
768 filename
Peter D Phan3beb02e2021-07-06 13:25:17 -0500769 """
Patrick Williams20f38712022-12-08 06:18:26 -0600770 self.logger.info(
771 "\n\t[Run] Executing commands on %s using %s"
772 % (self.hostname, ffdc_actions_for_target_type["PROTOCOL"][0])
773 )
Peter D Phan3beb02e2021-07-06 13:25:17 -0500774
George Keishingf5a57502021-07-22 16:43:47 -0500775 list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500776 # If command list is empty, returns
777 if not list_of_commands:
778 return
779
780 progress_counter = 0
781 for command in list_of_commands:
Peter D Phan5963d632021-07-12 09:58:55 -0500782 command_txt, command_timeout = self.unpack_command(command)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500783
784 if form_filename:
785 command_txt = str(command_txt % self.target_type)
786
Patrick Williams20f38712022-12-08 06:18:26 -0600787 (
788 cmd_exit_code,
789 err,
790 response,
791 ) = self.ssh_remoteclient.execute_command(
792 command_txt, command_timeout
793 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500794
795 if cmd_exit_code:
796 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600797 "\n\t\t[WARN] %s exits with code %s."
798 % (command_txt, str(cmd_exit_code))
799 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500800 self.logger.warning("\t\t[WARN] %s " % err)
Peter D Phanbabf2962021-07-07 11:24:40 -0500801
Peter D Phan3beb02e2021-07-06 13:25:17 -0500802 progress_counter += 1
803 self.print_progress(progress_counter)
804
Peter D Phane86d9a52021-07-15 10:42:25 -0500805 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan3beb02e2021-07-06 13:25:17 -0500806
Patrick Williams20f38712022-12-08 06:18:26 -0600807 def group_copy(self, ffdc_actions_for_target_type):
Peter D Phan56429a62021-06-23 08:38:29 -0500808 r"""
809 scp group of files (wild card) from remote host.
810
811 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530812 fdc_actions_for_target_type Commands and files for the selected
813 remote host type.
Peter D Phan56429a62021-06-23 08:38:29 -0500814 """
Peter D Phan3beb02e2021-07-06 13:25:17 -0500815
Peter D Phan5963d632021-07-12 09:58:55 -0500816 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600817 self.logger.info(
818 "\n\tCopying files from remote system %s via SCP.\n"
819 % self.hostname
820 )
Peter D Phan56429a62021-06-23 08:38:29 -0500821
Patrick Williams20f38712022-12-08 06:18:26 -0600822 list_of_commands = self.get_command_list(
823 ffdc_actions_for_target_type
824 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500825 # If command list is empty, returns
826 if not list_of_commands:
827 return
Peter D Phan56429a62021-06-23 08:38:29 -0500828
Peter D Phanbabf2962021-07-07 11:24:40 -0500829 for command in list_of_commands:
830 try:
George Keishingb4540e72021-08-02 13:48:46 -0500831 command = self.yaml_env_and_plugin_vars_populate(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500832 except IndexError:
George Keishingb4540e72021-08-02 13:48:46 -0500833 self.logger.error("\t\tInvalid command %s" % command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500834 continue
835
Patrick Williams20f38712022-12-08 06:18:26 -0600836 (
837 cmd_exit_code,
838 err,
839 response,
840 ) = self.ssh_remoteclient.execute_command(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500841
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500842 # If file does not exist, code take no action.
843 # cmd_exit_code is ignored for this scenario.
Peter D Phan56429a62021-06-23 08:38:29 -0500844 if response:
Patrick Williams20f38712022-12-08 06:18:26 -0600845 scp_result = self.ssh_remoteclient.scp_file_from_remote(
846 response.split("\n"), self.ffdc_dir_path
847 )
Peter D Phan56429a62021-06-23 08:38:29 -0500848 if scp_result:
Patrick Williams20f38712022-12-08 06:18:26 -0600849 self.logger.info(
850 "\t\tSuccessfully copied from "
851 + self.hostname
852 + ":"
853 + command
854 )
Peter D Phan56429a62021-06-23 08:38:29 -0500855 else:
George Keishinga56e87b2021-08-06 00:24:19 -0500856 self.logger.info("\t\t%s has no result" % command)
Peter D Phan56429a62021-06-23 08:38:29 -0500857
858 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600859 self.logger.info(
860 "\n\n\tSkip copying files from remote system %s.\n"
861 % self.hostname
862 )
Peter D Phan56429a62021-06-23 08:38:29 -0500863
Patrick Williams20f38712022-12-08 06:18:26 -0600864 def scp_ffdc(
865 self,
866 targ_dir_path,
867 targ_file_prefix,
868 form_filename,
869 file_list=None,
870 quiet=None,
871 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500872 r"""
George Keishingc754b432025-04-24 14:27:14 +0530873 SCP all files in file_dict to the indicated directory on the local
874 system.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500875
876 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530877 targ_dir_path The path of the directory to receive
878 the files.
George Keishinge16f1582022-12-15 07:32:21 -0600879 targ_file_prefix Prefix which will be prepended to each
Peter D Phan72ce6b82021-06-03 06:18:26 -0500880 target file's name.
George Keishingc754b432025-04-24 14:27:14 +0530881 file_dict A dictionary of files to scp from
882 targeted system to this system
Peter D Phan72ce6b82021-06-03 06:18:26 -0500883
884 """
885
Peter D Phan72ce6b82021-06-03 06:18:26 -0500886 progress_counter = 0
887 for filename in file_list:
Peter D Phan2b8052d2021-06-22 10:55:41 -0500888 if form_filename:
889 filename = str(filename % self.target_type)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500890 source_file_path = filename
Patrick Williams20f38712022-12-08 06:18:26 -0600891 targ_file_path = (
892 targ_dir_path + targ_file_prefix + filename.split("/")[-1]
893 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500894
Peter D Phanbabf2962021-07-07 11:24:40 -0500895 # If source file name contains wild card, copy filename as is.
Patrick Williams20f38712022-12-08 06:18:26 -0600896 if "*" in source_file_path:
897 scp_result = self.ssh_remoteclient.scp_file_from_remote(
898 source_file_path, self.ffdc_dir_path
899 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500900 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600901 scp_result = self.ssh_remoteclient.scp_file_from_remote(
902 source_file_path, targ_file_path
903 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500904
905 if not quiet:
906 if scp_result:
Peter D Phane86d9a52021-07-15 10:42:25 -0500907 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -0600908 "\t\tSuccessfully copied from "
909 + self.hostname
910 + ":"
911 + source_file_path
912 + ".\n"
913 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500914 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500915 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -0600916 "\t\tFail to copy from "
917 + self.hostname
918 + ":"
919 + source_file_path
920 + ".\n"
921 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500922 else:
923 progress_counter += 1
924 self.print_progress(progress_counter)
925
Peter D Phan5e56f522021-12-20 13:19:41 -0600926 def set_ffdc_default_store_path(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500927 r"""
928 Set a default value for self.ffdc_dir_path and self.ffdc_prefix.
George Keishingc754b432025-04-24 14:27:14 +0530929 Collected ffdc file will be stored in dir
930 /self.location/hostname_timestr/.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500931 Individual ffdc file will have timestr_filename.
932
933 Description of class variables:
George Keishingc754b432025-04-24 14:27:14 +0530934 self.ffdc_dir_path The dir path where collected ffdc data files
935 should be put.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500936
937 self.ffdc_prefix The prefix to be given to each ffdc file name.
938
939 """
940
941 timestr = time.strftime("%Y%m%d-%H%M%S")
Patrick Williams20f38712022-12-08 06:18:26 -0600942 self.ffdc_dir_path = (
943 self.location + "/" + self.hostname + "_" + timestr + "/"
944 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500945 self.ffdc_prefix = timestr + "_"
946 self.validate_local_store(self.ffdc_dir_path)
947
Peter D Phan5e56f522021-12-20 13:19:41 -0600948 # Need to verify local store path exists prior to instantiate this class.
949 # This class method is used to share the same code between CLI input parm
950 # and Robot Framework "${EXECDIR}/logs" before referencing this class.
951 @classmethod
952 def validate_local_store(cls, dir_path):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500953 r"""
954 Ensure path exists to store FFDC files locally.
955
956 Description of variable:
957 dir_path The dir path where collected ffdc data files will be stored.
958
959 """
960
961 if not os.path.exists(dir_path):
962 try:
George Keishing7b3a5132021-07-13 09:24:02 -0500963 os.makedirs(dir_path, 0o755)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500964 except (IOError, OSError) as e:
965 # PermissionError
966 if e.errno == EPERM or e.errno == EACCES:
George Keishing15352052025-04-24 18:55:47 +0530967 print(
Patrick Williams20f38712022-12-08 06:18:26 -0600968 "\tERROR: os.makedirs %s failed with"
969 " PermissionError.\n" % dir_path
970 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500971 else:
George Keishing15352052025-04-24 18:55:47 +0530972 print(
Patrick Williams20f38712022-12-08 06:18:26 -0600973 "\tERROR: os.makedirs %s failed with %s.\n"
974 % (dir_path, e.strerror)
975 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500976 sys.exit(-1)
977
978 def print_progress(self, progress):
979 r"""
980 Print activity progress +
981
982 Description of variable:
983 progress Progress counter.
984
985 """
986
987 sys.stdout.write("\r\t" + "+" * progress)
988 sys.stdout.flush()
Patrick Williams20f38712022-12-08 06:18:26 -0600989 time.sleep(0.1)
Peter D Phan0c669772021-06-24 13:52:42 -0500990
991 def verify_redfish(self):
992 r"""
993 Verify remote host has redfish service active
994
995 """
Patrick Williams20f38712022-12-08 06:18:26 -0600996 redfish_parm = (
997 "redfishtool -r "
998 + self.hostname
George Keishing7a61aa22023-06-26 13:18:37 +0530999 + ":"
1000 + self.port_https
Patrick Williams20f38712022-12-08 06:18:26 -06001001 + " -S Always raw GET /redfish/v1/"
1002 )
1003 return self.run_tool_cmd(redfish_parm, True)
Peter D Phan0c669772021-06-24 13:52:42 -05001004
George Keishingeafba182021-06-29 13:44:58 -05001005 def verify_ipmi(self):
1006 r"""
1007 Verify remote host has IPMI LAN service active
1008
1009 """
Patrick Williams20f38712022-12-08 06:18:26 -06001010 if self.target_type == "OPENBMC":
1011 ipmi_parm = (
1012 "ipmitool -I lanplus -C 17 -U "
1013 + self.username
1014 + " -P "
1015 + self.password
1016 + " -H "
1017 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +05301018 + " -p "
1019 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -06001020 + " power status"
1021 )
George Keishing484f8242021-07-27 01:42:02 -05001022 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001023 ipmi_parm = (
1024 "ipmitool -I lanplus -P "
1025 + self.password
1026 + " -H "
1027 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +05301028 + " -p "
1029 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -06001030 + " power status"
1031 )
George Keishing484f8242021-07-27 01:42:02 -05001032
Patrick Williams20f38712022-12-08 06:18:26 -06001033 return self.run_tool_cmd(ipmi_parm, True)
George Keishingeafba182021-06-29 13:44:58 -05001034
Patrick Williams20f38712022-12-08 06:18:26 -06001035 def run_tool_cmd(self, parms_string, quiet=False):
George Keishingeafba182021-06-29 13:44:58 -05001036 r"""
George Keishing506b0582021-07-27 09:31:22 -05001037 Run CLI standard tool or scripts.
George Keishingeafba182021-06-29 13:44:58 -05001038
1039 Description of variable:
George Keishing506b0582021-07-27 09:31:22 -05001040 parms_string tool command options.
1041 quiet do not print tool error message if True
George Keishingeafba182021-06-29 13:44:58 -05001042 """
1043
Patrick Williams20f38712022-12-08 06:18:26 -06001044 result = subprocess.run(
1045 [parms_string],
1046 stdout=subprocess.PIPE,
1047 stderr=subprocess.PIPE,
1048 shell=True,
1049 universal_newlines=True,
1050 )
George Keishingeafba182021-06-29 13:44:58 -05001051
1052 if result.stderr and not quiet:
George Keishing0e9b5ba2025-05-08 12:17:58 +05301053 if self.password in parms_string:
1054 parms_string = parms_string.replace(self.password, "********")
Patrick Williams20f38712022-12-08 06:18:26 -06001055 self.logger.error("\n\t\tERROR with %s " % parms_string)
1056 self.logger.error("\t\t" + result.stderr)
George Keishingeafba182021-06-29 13:44:58 -05001057
1058 return result.stdout
George Keishing04d29102021-07-16 02:05:57 -05001059
George Keishingf5a57502021-07-22 16:43:47 -05001060 def verify_protocol(self, protocol_list):
1061 r"""
1062 Perform protocol working check.
1063
1064 Description of argument(s):
1065 protocol_list List of protocol.
1066 """
1067
1068 tmp_list = []
1069 if self.target_is_pingable():
1070 tmp_list.append("SHELL")
1071
1072 for protocol in protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -06001073 if self.remote_protocol != "ALL":
George Keishingf5a57502021-07-22 16:43:47 -05001074 if self.remote_protocol != protocol:
1075 continue
1076
1077 # Only check SSH/SCP once for both protocols
Patrick Williams20f38712022-12-08 06:18:26 -06001078 if (
1079 protocol == "SSH"
1080 or protocol == "SCP"
1081 and protocol not in tmp_list
1082 ):
George Keishingf5a57502021-07-22 16:43:47 -05001083 if self.ssh_to_target_system():
George Keishingaa638702021-07-26 11:48:28 -05001084 # Add only what user asked.
Patrick Williams20f38712022-12-08 06:18:26 -06001085 if self.remote_protocol != "ALL":
George Keishingaa638702021-07-26 11:48:28 -05001086 tmp_list.append(self.remote_protocol)
1087 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001088 tmp_list.append("SSH")
1089 tmp_list.append("SCP")
George Keishingf5a57502021-07-22 16:43:47 -05001090
Patrick Williams20f38712022-12-08 06:18:26 -06001091 if protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -05001092 if self.telnet_to_target_system():
1093 tmp_list.append(protocol)
1094
Patrick Williams20f38712022-12-08 06:18:26 -06001095 if protocol == "REDFISH":
George Keishingf5a57502021-07-22 16:43:47 -05001096 if self.verify_redfish():
1097 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001098 self.logger.info(
1099 "\n\t[Check] %s Redfish Service.\t\t [OK]"
1100 % self.hostname
1101 )
George Keishingf5a57502021-07-22 16:43:47 -05001102 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001103 self.logger.info(
1104 "\n\t[Check] %s Redfish Service.\t\t [NOT AVAILABLE]"
1105 % self.hostname
1106 )
George Keishingf5a57502021-07-22 16:43:47 -05001107
Patrick Williams20f38712022-12-08 06:18:26 -06001108 if protocol == "IPMI":
George Keishingf5a57502021-07-22 16:43:47 -05001109 if self.verify_ipmi():
1110 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001111 self.logger.info(
1112 "\n\t[Check] %s IPMI LAN Service.\t\t [OK]"
1113 % self.hostname
1114 )
George Keishingf5a57502021-07-22 16:43:47 -05001115 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001116 self.logger.info(
1117 "\n\t[Check] %s IPMI LAN Service.\t\t [NOT AVAILABLE]"
1118 % self.hostname
1119 )
George Keishingf5a57502021-07-22 16:43:47 -05001120
1121 return tmp_list
George Keishinge1686752021-07-27 12:55:28 -05001122
1123 def load_env(self):
1124 r"""
George Keishing0e9b5ba2025-05-08 12:17:58 +05301125 Load the user environment variables from a YAML file.
George Keishinge1686752021-07-27 12:55:28 -05001126
George Keishing0e9b5ba2025-05-08 12:17:58 +05301127 This method reads the environment variables from a YAML file specified
1128 in the ENV_FILE environment variable. If the file is not found or
1129 there is an error reading the file, an exception is raised.
1130
1131 The YAML file should have the following format:
1132
1133 .. code-block:: yaml
1134
1135 VAR_NAME: VAR_VALUE
1136
1137 Where VAR_NAME is the name of the environment variable, and
1138 VAR_VALUE is its value.
1139
1140 After loading the environment variables, they are stored in the
1141 self.env attribute for later use.
George Keishinge1686752021-07-27 12:55:28 -05001142 """
George Keishing0e9b5ba2025-05-08 12:17:58 +05301143
Patrick Williams20f38712022-12-08 06:18:26 -06001144 os.environ["hostname"] = self.hostname
1145 os.environ["username"] = self.username
1146 os.environ["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301147 os.environ["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301148 os.environ["port_https"] = self.port_https
1149 os.environ["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001150
1151 # Append default Env.
Patrick Williams20f38712022-12-08 06:18:26 -06001152 self.env_dict["hostname"] = self.hostname
1153 self.env_dict["username"] = self.username
1154 self.env_dict["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301155 self.env_dict["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301156 self.env_dict["port_https"] = self.port_https
1157 self.env_dict["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001158
1159 try:
1160 tmp_env_dict = {}
1161 if self.env_vars:
1162 tmp_env_dict = json.loads(self.env_vars)
1163 # Export ENV vars default.
1164 for key, value in tmp_env_dict.items():
1165 os.environ[key] = value
1166 self.env_dict[key] = str(value)
1167
George Keishing0e9b5ba2025-05-08 12:17:58 +05301168 # Load user specified ENV config YAML.
George Keishinge1686752021-07-27 12:55:28 -05001169 if self.econfig:
Patrick Williams20f38712022-12-08 06:18:26 -06001170 with open(self.econfig, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -05001171 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -07001172 tmp_env_dict = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -05001173 except yaml.YAMLError as e:
1174 self.logger.error(e)
1175 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001176 # Export ENV vars.
Patrick Williams20f38712022-12-08 06:18:26 -06001177 for key, value in tmp_env_dict["env_params"].items():
George Keishinge1686752021-07-27 12:55:28 -05001178 os.environ[key] = str(value)
1179 self.env_dict[key] = str(value)
1180 except json.decoder.JSONDecodeError as e:
1181 self.logger.error("\n\tERROR: %s " % e)
1182 sys.exit(-1)
George Keishing0e9b5ba2025-05-08 12:17:58 +05301183 except FileNotFoundError as e:
1184 self.logger.error("\n\tERROR: %s " % e)
1185 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001186
1187 # This to mask the password from displaying on the console.
1188 mask_dict = self.env_dict.copy()
1189 for k, v in mask_dict.items():
1190 if k.lower().find("password") != -1:
1191 hidden_text = []
1192 hidden_text.append(v)
Patrick Williams20f38712022-12-08 06:18:26 -06001193 password_regex = (
1194 "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1195 )
George Keishinge1686752021-07-27 12:55:28 -05001196 mask_dict[k] = re.sub(password_regex, "********", v)
1197
1198 self.logger.info(json.dumps(mask_dict, indent=8, sort_keys=False))
George Keishingb97a9042021-07-29 07:41:20 -05001199
1200 def execute_python_eval(self, eval_string):
1201 r"""
George Keishing9348b402021-08-13 12:22:35 -05001202 Execute qualified python function string using eval.
George Keishingb97a9042021-07-29 07:41:20 -05001203
1204 Description of argument(s):
1205 eval_string Execute the python object.
1206
1207 Example:
1208 eval(plugin.foo_func.foo_func(10))
1209 """
1210 try:
George Keishingdda48ce2021-08-12 07:02:27 -05001211 self.logger.info("\tExecuting plugin func()")
1212 self.logger.debug("\tCall func: %s" % eval_string)
George Keishingb97a9042021-07-29 07:41:20 -05001213 result = eval(eval_string)
1214 self.logger.info("\treturn: %s" % str(result))
Patrick Williams20f38712022-12-08 06:18:26 -06001215 except (
1216 ValueError,
1217 SyntaxError,
1218 NameError,
1219 AttributeError,
1220 TypeError,
1221 ) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001222 self.logger.error("\tERROR: execute_python_eval: %s" % e)
1223 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001224 plugin_error_dict["exit_on_error"] = True
George Keishing73b95d12021-08-13 14:30:52 -05001225 self.logger.info("\treturn: PLUGIN_EVAL_ERROR")
Patrick Williams20f38712022-12-08 06:18:26 -06001226 return "PLUGIN_EVAL_ERROR"
George Keishingb97a9042021-07-29 07:41:20 -05001227
1228 return result
1229
1230 def execute_plugin_block(self, plugin_cmd_list):
1231 r"""
Peter D Phan5e56f522021-12-20 13:19:41 -06001232 Pack the plugin command to qualifed python string object.
George Keishingb97a9042021-07-29 07:41:20 -05001233
1234 Description of argument(s):
1235 plugin_list_dict Plugin block read from YAML
1236 [{'plugin_name': 'plugin.foo_func.my_func'},
1237 {'plugin_args': [10]}]
1238
1239 Example:
1240 - plugin:
1241 - plugin_name: plugin.foo_func.my_func
1242 - plugin_args:
1243 - arg1
1244 - arg2
1245
1246 - plugin:
1247 - plugin_name: result = plugin.foo_func.my_func
1248 - plugin_args:
1249 - arg1
1250 - arg2
1251
1252 - plugin:
1253 - plugin_name: result1,result2 = plugin.foo_func.my_func
1254 - plugin_args:
1255 - arg1
1256 - arg2
1257 """
1258 try:
Patrick Williams20f38712022-12-08 06:18:26 -06001259 idx = self.key_index_list_dict("plugin_name", plugin_cmd_list)
1260 plugin_name = plugin_cmd_list[idx]["plugin_name"]
George Keishingb97a9042021-07-29 07:41:20 -05001261 # Equal separator means plugin function returns result.
Patrick Williams20f38712022-12-08 06:18:26 -06001262 if " = " in plugin_name:
George Keishingb97a9042021-07-29 07:41:20 -05001263 # Ex. ['result', 'plugin.foo_func.my_func']
Patrick Williams20f38712022-12-08 06:18:26 -06001264 plugin_name_args = plugin_name.split(" = ")
George Keishingb97a9042021-07-29 07:41:20 -05001265 # plugin func return data.
1266 for arg in plugin_name_args:
1267 if arg == plugin_name_args[-1]:
1268 plugin_name = arg
1269 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001270 plugin_resp = arg.split(",")
George Keishingb97a9042021-07-29 07:41:20 -05001271 # ['result1','result2']
1272 for x in plugin_resp:
1273 global_plugin_list.append(x)
1274 global_plugin_dict[x] = ""
1275
1276 # Walk the plugin args ['arg1,'arg2']
1277 # If the YAML plugin statement 'plugin_args' is not declared.
George Keishingf0eb1d62025-05-14 15:07:02 +05301278 plugin_args = []
Patrick Williams20f38712022-12-08 06:18:26 -06001279 if any("plugin_args" in d for d in plugin_cmd_list):
1280 idx = self.key_index_list_dict("plugin_args", plugin_cmd_list)
George Keishingf0eb1d62025-05-14 15:07:02 +05301281 if idx is not None:
1282 plugin_args = plugin_cmd_list[idx].get("plugin_args", [])
George Keishingb97a9042021-07-29 07:41:20 -05001283 plugin_args = self.yaml_args_populate(plugin_args)
1284 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301285 plugin_args = self.yaml_args_populate([])
George Keishingb97a9042021-07-29 07:41:20 -05001286
George Keishing450f92f2025-05-15 23:12:51 +05301287 # Pack the args list to string parameters for plugin function.
George Keishingb97a9042021-07-29 07:41:20 -05001288 parm_args_str = self.yaml_args_string(plugin_args)
George Keishing450f92f2025-05-15 23:12:51 +05301289
1290 """
1291 Example of plugin_func:
1292 plugin.redfish.enumerate_request(
1293 "xx.xx.xx.xx:443",
1294 "root",
1295 "********",
1296 "/redfish/v1/",
1297 "json")
1298 """
George Keishingb97a9042021-07-29 07:41:20 -05001299 if parm_args_str:
George Keishing450f92f2025-05-15 23:12:51 +05301300 plugin_func = f"{plugin_name}({parm_args_str})"
George Keishingb97a9042021-07-29 07:41:20 -05001301 else:
George Keishing450f92f2025-05-15 23:12:51 +05301302 plugin_func = f"{plugin_name}()"
George Keishingb97a9042021-07-29 07:41:20 -05001303
1304 # Execute plugin function.
1305 if global_plugin_dict:
1306 resp = self.execute_python_eval(plugin_func)
George Keishing9348b402021-08-13 12:22:35 -05001307 # Update plugin vars dict if there is any.
Patrick Williams20f38712022-12-08 06:18:26 -06001308 if resp != "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001309 self.response_args_data(resp)
George Keishingb97a9042021-07-29 07:41:20 -05001310 else:
George Keishingcaa97e62021-08-03 14:00:09 -05001311 resp = self.execute_python_eval(plugin_func)
George Keishingb97a9042021-07-29 07:41:20 -05001312 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001313 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001314 plugin_error_dict["exit_on_error"] = True
George Keishing1e7b0182021-08-06 14:05:54 -05001315 self.logger.error("\tERROR: execute_plugin_block: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001316 pass
1317
George Keishing73b95d12021-08-13 14:30:52 -05001318 # There is a real error executing the plugin function.
Patrick Williams20f38712022-12-08 06:18:26 -06001319 if resp == "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001320 return resp
1321
George Keishingde79a9b2021-08-12 16:14:43 -05001322 # Check if plugin_expects_return (int, string, list,dict etc)
Patrick Williams20f38712022-12-08 06:18:26 -06001323 if any("plugin_expects_return" in d for d in plugin_cmd_list):
1324 idx = self.key_index_list_dict(
1325 "plugin_expects_return", plugin_cmd_list
1326 )
1327 plugin_expects = plugin_cmd_list[idx]["plugin_expects_return"]
George Keishingde79a9b2021-08-12 16:14:43 -05001328 if plugin_expects:
1329 if resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001330 if (
1331 self.plugin_expect_type(plugin_expects, resp)
1332 == "INVALID"
1333 ):
George Keishingde79a9b2021-08-12 16:14:43 -05001334 self.logger.error("\tWARN: Plugin error check skipped")
1335 elif not self.plugin_expect_type(plugin_expects, resp):
Patrick Williams20f38712022-12-08 06:18:26 -06001336 self.logger.error(
1337 "\tERROR: Plugin expects return data: %s"
1338 % plugin_expects
1339 )
1340 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001341 elif not resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001342 self.logger.error(
1343 "\tERROR: Plugin func failed to return data"
1344 )
1345 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001346
1347 return resp
1348
George Keishingb97a9042021-07-29 07:41:20 -05001349 def response_args_data(self, plugin_resp):
1350 r"""
George Keishing9348b402021-08-13 12:22:35 -05001351 Parse the plugin function response and update plugin return variable.
George Keishingb97a9042021-07-29 07:41:20 -05001352
1353 plugin_resp Response data from plugin function.
1354 """
1355 resp_list = []
George Keishing5765f792021-08-02 13:08:53 -05001356 resp_data = ""
George Keishing9348b402021-08-13 12:22:35 -05001357
George Keishingb97a9042021-07-29 07:41:20 -05001358 # There is nothing to update the plugin response.
Patrick Williams20f38712022-12-08 06:18:26 -06001359 if len(global_plugin_list) == 0 or plugin_resp == "None":
George Keishingb97a9042021-07-29 07:41:20 -05001360 return
1361
George Keishing5765f792021-08-02 13:08:53 -05001362 if isinstance(plugin_resp, str):
Patrick Williams20f38712022-12-08 06:18:26 -06001363 resp_data = plugin_resp.strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001364 resp_list.append(resp_data)
1365 elif isinstance(plugin_resp, bytes):
Patrick Williams20f38712022-12-08 06:18:26 -06001366 resp_data = str(plugin_resp, "UTF-8").strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001367 resp_list.append(resp_data)
1368 elif isinstance(plugin_resp, tuple):
1369 if len(global_plugin_list) == 1:
George Keishingb97a9042021-07-29 07:41:20 -05001370 resp_list.append(plugin_resp)
George Keishing5765f792021-08-02 13:08:53 -05001371 else:
1372 resp_list = list(plugin_resp)
Patrick Williams20f38712022-12-08 06:18:26 -06001373 resp_list = [x.strip("\r\n\t") for x in resp_list]
George Keishingb97a9042021-07-29 07:41:20 -05001374 elif isinstance(plugin_resp, list):
George Keishing5765f792021-08-02 13:08:53 -05001375 if len(global_plugin_list) == 1:
Patrick Williams20f38712022-12-08 06:18:26 -06001376 resp_list.append([x.strip("\r\n\t") for x in plugin_resp])
George Keishing5765f792021-08-02 13:08:53 -05001377 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001378 resp_list = [x.strip("\r\n\t") for x in plugin_resp]
George Keishing5765f792021-08-02 13:08:53 -05001379 elif isinstance(plugin_resp, int) or isinstance(plugin_resp, float):
1380 resp_list.append(plugin_resp)
George Keishingb97a9042021-07-29 07:41:20 -05001381
George Keishing9348b402021-08-13 12:22:35 -05001382 # Iterate if there is a list of plugin return vars to update.
George Keishingb97a9042021-07-29 07:41:20 -05001383 for idx, item in enumerate(resp_list, start=0):
George Keishing9348b402021-08-13 12:22:35 -05001384 # Exit loop, done required loop.
George Keishingb97a9042021-07-29 07:41:20 -05001385 if idx >= len(global_plugin_list):
1386 break
1387 # Find the index of the return func in the list and
1388 # update the global func return dictionary.
1389 try:
1390 dict_idx = global_plugin_list[idx]
1391 global_plugin_dict[dict_idx] = item
1392 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001393 self.logger.warn("\tWARN: response_args_data: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001394 pass
1395
1396 # Done updating plugin dict irrespective of pass or failed,
George Keishing9348b402021-08-13 12:22:35 -05001397 # clear all the list element for next plugin block execute.
George Keishingb97a9042021-07-29 07:41:20 -05001398 global_plugin_list.clear()
1399
1400 def yaml_args_string(self, plugin_args):
1401 r"""
George Keishing450f92f2025-05-15 23:12:51 +05301402 Pack the arguments into a string representation.
George Keishingb97a9042021-07-29 07:41:20 -05001403
George Keishing450f92f2025-05-15 23:12:51 +05301404 This method processes the plugin_arg argument, which is expected to
1405 contain a list of arguments. The method iterates through the list,
1406 converts each argument to a string, and concatenates them into a
1407 single string. Special handling is applied for integer, float, and
1408 predefined plugin variable types.
1409
1410 Ecample:
1411 From
1412 ['xx.xx.xx.xx:443', 'root', '********', '/redfish/v1/', 'json']
1413 to
1414 "xx.xx.xx.xx:443","root","********","/redfish/v1/","json"
1415
1416 Parameters:
1417 plugin_args (list): A list of arguments to be packed into
1418 a string.
1419
1420 Returns:
1421 str: A string representation of the arguments.
George Keishingb97a9042021-07-29 07:41:20 -05001422 """
Patrick Williams20f38712022-12-08 06:18:26 -06001423 args_str = ""
George Keishing450f92f2025-05-15 23:12:51 +05301424
1425 for i, arg in enumerate(plugin_args):
1426 if arg:
1427 if isinstance(arg, (int, float)):
1428 args_str += str(arg)
1429 elif arg in global_plugin_type_list:
1430 args_str += str(global_plugin_dict[arg])
George Keishingb97a9042021-07-29 07:41:20 -05001431 else:
George Keishing450f92f2025-05-15 23:12:51 +05301432 args_str += f'"{arg.strip("\r\n\t")}"'
1433
1434 # Skip last list element.
1435 if i != len(plugin_args) - 1:
1436 args_str += ","
1437
George Keishingb97a9042021-07-29 07:41:20 -05001438 return args_str
1439
1440 def yaml_args_populate(self, yaml_arg_list):
1441 r"""
George Keishingf0eb1d62025-05-14 15:07:02 +05301442 Decode environment and plugin variables and populate the argument list.
George Keishingb97a9042021-07-29 07:41:20 -05001443
George Keishingf0eb1d62025-05-14 15:07:02 +05301444 This method processes the yaml_arg_list argument, which is expected to
1445 contain a list of arguments read from a YAML file. The method iterates
1446 through the list, decodes environment and plugin variables, and
1447 returns a populated list of arguments.
George Keishingb97a9042021-07-29 07:41:20 -05001448
George Keishingf0eb1d62025-05-14 15:07:02 +05301449 .. code-block:: yaml
1450
George Keishingb97a9042021-07-29 07:41:20 -05001451 - plugin_args:
1452 - arg1
1453 - arg2
1454
George Keishingf0eb1d62025-05-14 15:07:02 +05301455 ['${hostname}:${port_https}', '${username}', '/redfish/v1/', 'json']
George Keishingb97a9042021-07-29 07:41:20 -05001456
George Keishingf0eb1d62025-05-14 15:07:02 +05301457 Returns the populated plugin list
1458 ['xx.xx.xx.xx:443', 'root', '/redfish/v1/', 'json']
1459
1460 Parameters:
1461 yaml_arg_list (list): A list of arguments containing environment
1462 and plugin variables.
1463
1464 Returns:
1465 list: A populated list of arguments with decoded environment and
1466 plugin variables.
1467 """
George Keishingb97a9042021-07-29 07:41:20 -05001468 if isinstance(yaml_arg_list, list):
George Keishingf0eb1d62025-05-14 15:07:02 +05301469 populated_list = []
George Keishingb97a9042021-07-29 07:41:20 -05001470 for arg in yaml_arg_list:
George Keishing0581cb02021-08-05 15:08:58 -05001471 if isinstance(arg, (int, float)):
George Keishingf0eb1d62025-05-14 15:07:02 +05301472 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001473 elif isinstance(arg, str):
1474 arg_str = self.yaml_env_and_plugin_vars_populate(str(arg))
George Keishingf0eb1d62025-05-14 15:07:02 +05301475 populated_list.append(arg_str)
George Keishingb97a9042021-07-29 07:41:20 -05001476 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301477 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001478
George Keishingf0eb1d62025-05-14 15:07:02 +05301479 return populated_list
George Keishingb97a9042021-07-29 07:41:20 -05001480
1481 def yaml_env_and_plugin_vars_populate(self, yaml_arg_str):
1482 r"""
George Keishinga593f4b2025-05-13 20:02:36 +05301483 Update environment variables and plugin variables based on the
1484 provided YAML argument string.
George Keishingb97a9042021-07-29 07:41:20 -05001485
George Keishinga593f4b2025-05-13 20:02:36 +05301486 This method processes the yaml_arg_str argument, which is expected
1487 to contain a string representing environment variables and plugin
1488 variables in the format:
George Keishingb97a9042021-07-29 07:41:20 -05001489
George Keishinga593f4b2025-05-13 20:02:36 +05301490 .. code-block:: yaml
1491
George Keishingb97a9042021-07-29 07:41:20 -05001492 - cat ${MY_VAR}
1493 - ls -AX my_plugin_var
George Keishinga593f4b2025-05-13 20:02:36 +05301494
1495 The method parses the string, extracts the variable names, and updates
1496 the corresponding environment variables and plugin variables.
1497
1498 Parameters:
1499 yaml_arg_str (str): A string containing environment and plugin
1500 variable definitions in YAML format.
1501
1502 Returns:
1503 str: The updated YAML argument string with plugin variables
1504 replaced.
George Keishingb97a9042021-07-29 07:41:20 -05001505 """
George Keishinga593f4b2025-05-13 20:02:36 +05301506
1507 # Parse and convert the Plugin YAML vars string to python vars
1508 # Example:
1509 # ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
George Keishingb97a9042021-07-29 07:41:20 -05001510 try:
George Keishingc754b432025-04-24 14:27:14 +05301511 # Example, list of matching
1512 # env vars ['username', 'password', 'hostname']
George Keishingb97a9042021-07-29 07:41:20 -05001513 # Extra escape \ for special symbols. '\$\{([^\}]+)\}' works good.
George Keishinga593f4b2025-05-13 20:02:36 +05301514 env_var_regex = r"\$\{([^\}]+)\}"
1515 env_var_names_list = re.findall(env_var_regex, yaml_arg_str)
1516
George Keishingb97a9042021-07-29 07:41:20 -05001517 for var in env_var_names_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301518 env_var = os.environ.get(var)
1519 if env_var:
1520 env_replace = "${" + var + "}"
1521 yaml_arg_str = yaml_arg_str.replace(env_replace, env_var)
George Keishingb97a9042021-07-29 07:41:20 -05001522 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001523 self.logger.error("\tERROR:yaml_env_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001524 pass
1525
George Keishinga593f4b2025-05-13 20:02:36 +05301526 """
1527 Parse the string for plugin vars.
1528 Implement the logic to update environment variables based on the
1529 extracted variable names.
1530 """
George Keishingb97a9042021-07-29 07:41:20 -05001531 try:
George Keishinga593f4b2025-05-13 20:02:36 +05301532 # Example, list of plugin vars env_var_names_list
1533 # ['my_hostname', 'port_https']
1534 global_plugin_dict_keys = set(global_plugin_dict.keys())
1535 # Skip env var list already populated above code block list.
1536 plugin_var_name_list = [
1537 var
1538 for var in global_plugin_dict_keys
1539 if var not in env_var_names_list
1540 ]
1541
George Keishingb97a9042021-07-29 07:41:20 -05001542 for var in plugin_var_name_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301543 plugin_var_value = global_plugin_dict[var]
George Keishing0581cb02021-08-05 15:08:58 -05001544 if yaml_arg_str in global_plugin_dict:
George Keishinga593f4b2025-05-13 20:02:36 +05301545 """
1546 If this plugin var exist but empty in dict, don't replace.
1547 his is either a YAML plugin statement incorrectly used or
1548 user added a plugin var which is not going to be populated.
1549 """
1550 if isinstance(plugin_var_value, (list, dict)):
1551 """
1552 List data type or dict can't be replaced, use
1553 directly in eval function call.
1554 """
George Keishing0581cb02021-08-05 15:08:58 -05001555 global_plugin_type_list.append(var)
1556 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001557 yaml_arg_str = yaml_arg_str.replace(
George Keishinga593f4b2025-05-13 20:02:36 +05301558 str(var), str(plugin_var_value)
Patrick Williams20f38712022-12-08 06:18:26 -06001559 )
George Keishingb97a9042021-07-29 07:41:20 -05001560 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001561 self.logger.error("\tERROR: yaml_plugin_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001562 pass
1563
George Keishinga593f4b2025-05-13 20:02:36 +05301564 # From ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
1565 # to populated values string as
1566 # Example: xx.xx.xx.xx:443 and return the string
George Keishingb97a9042021-07-29 07:41:20 -05001567 return yaml_arg_str
George Keishing1e7b0182021-08-06 14:05:54 -05001568
1569 def plugin_error_check(self, plugin_dict):
1570 r"""
George Keishing1e877422025-05-09 20:45:09 +05301571 Process plugin error dictionary and return the corresponding error
1572 message.
George Keishing1e7b0182021-08-06 14:05:54 -05001573
George Keishing1e877422025-05-09 20:45:09 +05301574 This method checks if any dictionary in the plugin_dict list contains
1575 a "plugin_error" key. If such a dictionary is found, it retrieves the
1576 value associated with the "plugin_error" key and returns the
1577 corresponding error message from the plugin_error_dict attribute.
1578
1579 Parameters:
1580 plugin_dict (list of dict): A list of dictionaries containing
1581 plugin error information.
1582
1583 Returns:
1584 str: The error message corresponding to the "plugin_error" value,
1585 or None if no error is found.
George Keishing1e7b0182021-08-06 14:05:54 -05001586 """
Patrick Williams20f38712022-12-08 06:18:26 -06001587 if any("plugin_error" in d for d in plugin_dict):
George Keishing1e7b0182021-08-06 14:05:54 -05001588 for d in plugin_dict:
Patrick Williams20f38712022-12-08 06:18:26 -06001589 if "plugin_error" in d:
1590 value = d["plugin_error"]
George Keishing1e877422025-05-09 20:45:09 +05301591 return self.plugin_error_dict.get(value, None)
1592 return None
George Keishingde79a9b2021-08-12 16:14:43 -05001593
1594 def key_index_list_dict(self, key, list_dict):
1595 r"""
George Keishing1e877422025-05-09 20:45:09 +05301596 Find the index of the first dictionary in the list that contains
1597 the specified key.
George Keishingde79a9b2021-08-12 16:14:43 -05001598
George Keishing1e877422025-05-09 20:45:09 +05301599 Parameters:
1600 key (str): The key to search for in the
1601 dictionaries.
1602 list_dict (list of dict): A list of dictionaries to search
1603 through.
1604
1605 Returns:
1606 int: The index of the first dictionary containing the key, or -1
1607 if no match is found.
George Keishingde79a9b2021-08-12 16:14:43 -05001608 """
1609 for i, d in enumerate(list_dict):
George Keishing1e877422025-05-09 20:45:09 +05301610 if key in d:
George Keishingde79a9b2021-08-12 16:14:43 -05001611 return i
George Keishing1e877422025-05-09 20:45:09 +05301612 return -1
George Keishingde79a9b2021-08-12 16:14:43 -05001613
1614 def plugin_expect_type(self, type, data):
1615 r"""
George Keishing1e877422025-05-09 20:45:09 +05301616 Check if the provided data matches the expected type.
1617
1618 This method checks if the data argument matches the specified type.
1619 It supports the following types: "int", "float", "str", "list", "dict",
1620 and "tuple".
1621
1622 If the type is not recognized, it logs an info message and returns
1623 "INVALID".
1624
1625 Parameters:
1626 type (str): The expected data type.
1627 data: The data to check against the expected type.
1628
1629 Returns:
1630 bool or str: True if the data matches the expected type, False if
1631 not, or "INVALID" if the type is not recognized.
George Keishingde79a9b2021-08-12 16:14:43 -05001632 """
Patrick Williams20f38712022-12-08 06:18:26 -06001633 if type == "int":
George Keishingde79a9b2021-08-12 16:14:43 -05001634 return isinstance(data, int)
Patrick Williams20f38712022-12-08 06:18:26 -06001635 elif type == "float":
George Keishingde79a9b2021-08-12 16:14:43 -05001636 return isinstance(data, float)
Patrick Williams20f38712022-12-08 06:18:26 -06001637 elif type == "str":
George Keishingde79a9b2021-08-12 16:14:43 -05001638 return isinstance(data, str)
Patrick Williams20f38712022-12-08 06:18:26 -06001639 elif type == "list":
George Keishingde79a9b2021-08-12 16:14:43 -05001640 return isinstance(data, list)
Patrick Williams20f38712022-12-08 06:18:26 -06001641 elif type == "dict":
George Keishingde79a9b2021-08-12 16:14:43 -05001642 return isinstance(data, dict)
Patrick Williams20f38712022-12-08 06:18:26 -06001643 elif type == "tuple":
George Keishingde79a9b2021-08-12 16:14:43 -05001644 return isinstance(data, tuple)
1645 else:
1646 self.logger.info("\tInvalid data type requested: %s" % type)
Patrick Williams20f38712022-12-08 06:18:26 -06001647 return "INVALID"