blob: f8708adef5e19f4278b02961ecd8d8c450dfc460 [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
George Keishing7bc5ce32025-05-19 19:15:20 +05307import importlib
Patrick Williams20f38712022-12-08 06:18:26 -06008import json
9import logging
10import os
11import platform
12import re
13import subprocess
14import sys
15import time
George Keishing09679892022-12-08 08:21:52 -060016from errno import EACCES, EPERM
George Keishing7bc5ce32025-05-19 19:15:20 +053017from typing import Any
George Keishing09679892022-12-08 08:21:52 -060018
George Keishinge635ddc2022-12-08 07:38:02 -060019import yaml
Peter D Phan5e56f522021-12-20 13:19:41 -060020
George Keishing168545d2025-05-12 19:26:54 +053021sys.dont_write_bytecode = True
22
23
Peter D Phancb791d72022-02-08 12:23:03 -060024script_dir = os.path.dirname(os.path.abspath(__file__))
25sys.path.append(script_dir)
26# Walk path and append to sys.path
27for root, dirs, files in os.walk(script_dir):
28 for dir in dirs:
29 sys.path.append(os.path.join(root, dir))
30
Patrick Williams20f38712022-12-08 06:18:26 -060031from ssh_utility import SSHRemoteclient # NOQA
32from telnet_utility import TelnetRemoteclient # NOQA
Peter D Phan72ce6b82021-06-03 06:18:26 -050033
George Keishingb97a9042021-07-29 07:41:20 -050034r"""
George Keishingb97a9042021-07-29 07:41:20 -050035This is for plugin functions returning data or responses to the caller
36in YAML plugin setup.
37
38Example:
39
40 - plugin:
George Keishing7bc5ce32025-05-19 19:15:20 +053041 - plugin_name: plugin.ssh_execution
42 - plugin_function: version = ssh_execute_cmd
George Keishingb97a9042021-07-29 07:41:20 -050043 - plugin_args:
44 - ${hostname}
45 - ${username}
46 - ${password}
47 - "cat /etc/os-release | grep VERSION_ID | awk -F'=' '{print $2}'"
48 - plugin:
George Keishing7bc5ce32025-05-19 19:15:20 +053049 - plugin_name: plugin.print_vars
50 - plugin_function: print_vars
George Keishingb97a9042021-07-29 07:41:20 -050051 - plugin_args:
52 - version
53
54where first plugin "version" var is used by another plugin in the YAML
55block or plugin
56
57"""
George Keishing7bc5ce32025-05-19 19:15:20 +053058# Global variables for storing plugin return values, plugin return variables,
59# and log storage path.
George Keishingb97a9042021-07-29 07:41:20 -050060global global_log_store_path
61global global_plugin_dict
62global global_plugin_list
George Keishing7bc5ce32025-05-19 19:15:20 +053063global global_plugin_type_list
64global global_plugin_error_dict
George Keishing9348b402021-08-13 12:22:35 -050065
George Keishing7bc5ce32025-05-19 19:15:20 +053066# Hold the plugin return values in a dictionary and plugin return variables in
67# a list. The dictionary is used for referencing and updating variables during
68# parsing in the parser, while the list is used for storing current variables
69# from the plugin block that need processing.
George Keishingb97a9042021-07-29 07:41:20 -050070global_plugin_dict = {}
71global_plugin_list = []
George Keishing9348b402021-08-13 12:22:35 -050072
George Keishing7bc5ce32025-05-19 19:15:20 +053073# Hold the plugin return named variables if the function returned values are
74# lists or dictionaries. This list is used to reference the plugin dictionary
75# for python function execute arguments.
76# Example: ['version']
George Keishing0581cb02021-08-05 15:08:58 -050077global_plugin_type_list = []
George Keishing9348b402021-08-13 12:22:35 -050078
79# Path where logs are to be stored or written.
Patrick Williams20f38712022-12-08 06:18:26 -060080global_log_store_path = ""
George Keishingb97a9042021-07-29 07:41:20 -050081
George Keishing1e7b0182021-08-06 14:05:54 -050082# Plugin error state defaults.
George Keishing7bc5ce32025-05-19 19:15:20 +053083global_plugin_error_dict = {
Patrick Williams20f38712022-12-08 06:18:26 -060084 "exit_on_error": False,
85 "continue_on_error": False,
George Keishing1e7b0182021-08-06 14:05:54 -050086}
87
Peter D Phan72ce6b82021-06-03 06:18:26 -050088
George Keishing7bc5ce32025-05-19 19:15:20 +053089def execute_python_function(module_name, function_name, *args, **kwargs):
90 r"""
91 Execute a Python function from a module dynamically.
92
93 This function dynamically imports a module and executes a specified
94 function from that module with the provided arguments. The function takes
95 the module name, function name, and arguments as input. The function
96 returns the result of the executed function.
97
98 If an ImportError or AttributeError occurs, the function prints an error
99 message and returns None.
100
101 Parameters:
102 module_name (str): The name of the module containing the function.
103 function_name (str): The name of the function to execute.
104 *args: Positional arguments to pass to the function.
105 **kwargs: Keyword arguments to pass to the function.
106
107 Returns:
108 Any: The result of the executed function or None if an error occurs.
109 """
110 try:
111 # Dynamically import the module.
112 module = importlib.import_module(module_name)
113
114 # Get the function from the module.
115 func = getattr(module, function_name)
116
117 # Call the function with the provided arguments.
118 result = func(*args, **kwargs)
119
120 except (ImportError, AttributeError) as e:
121 print(f"\tERROR: execute_python_function: {e}")
122 # Set the plugin error state.
123 global_plugin_error_dict["exit_on_error"] = True
124 print("\treturn: PLUGIN_EXEC_ERROR")
125 return "PLUGIN_EXEC_ERROR"
126
127 return result
128
129
Peter D Phan5e56f522021-12-20 13:19:41 -0600130class ffdc_collector:
Peter D Phan72ce6b82021-06-03 06:18:26 -0500131 r"""
George Keishing7bc5ce32025-05-19 19:15:20 +0530132 Execute commands from a configuration file to collect log files and store
133 the generated files at the specified location.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500134
George Keishing7bc5ce32025-05-19 19:15:20 +0530135 This class is designed to execute commands specified in a configuration
136 YAML file to collect log files from a remote host.
137
138 The class establishes connections using SSH, Telnet, or other protocols
139 based on the configuration. It fetches and stores the generated files at
140 the specified location. The class provides methods for initializing the
141 collector, executing commands, and handling errors.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500142 """
143
Patrick Williams20f38712022-12-08 06:18:26 -0600144 def __init__(
145 self,
146 hostname,
147 username,
148 password,
George Keishing7a61aa22023-06-26 13:18:37 +0530149 port_ssh,
George Keishinge8a41752023-06-22 21:42:47 +0530150 port_https,
151 port_ipmi,
Patrick Williams20f38712022-12-08 06:18:26 -0600152 ffdc_config,
153 location,
154 remote_type,
155 remote_protocol,
156 env_vars,
157 econfig,
158 log_level,
159 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500160 r"""
George Keishing04eb44e2025-05-16 22:14:14 +0530161 Initialize the FFDCCollector object with the provided parameters.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500162
George Keishing04eb44e2025-05-16 22:14:14 +0530163 This method initializes an FFDCCollector object with the given
164 attributes. The attributes represent the configuration for connecting
165 to a remote system, collecting log data, and storing the collected
166 data.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500167
George Keishing04eb44e2025-05-16 22:14:14 +0530168 Parameters:
169 hostname (str): Name or IP address of the targeted
170 (remote) system.
171 username (str): User on the targeted system with access
172 to log files.
173 password (str): Password for the user on the targeted
174 system.
175 port_ssh (int, optional): SSH port value. Defaults to 22.
176 port_https (int, optional): HTTPS port value. Defaults to 443.
177 port_ipmi (int, optional): IPMI port value. Defaults to 623.
178 ffdc_config (str): Configuration file listing commands
179 and files for FFDC.
180 location (str): Where to store collected log data.
181 remote_type (str): Block YAML type name of the remote
182 host.
183 remote_protocol (str): Protocol to use to collect data.
184 env_vars (dict, optional): User-defined CLI environment variables.
185 Defaults to None.
186 econfig (str, optional): User-defined environment variables
187 YAML file. Defaults to None.
188 log_level (str, optional): Log level for the collector.
189 Defaults to "INFO".
Peter D Phan72ce6b82021-06-03 06:18:26 -0500190 """
Peter D Phane86d9a52021-07-15 10:42:25 -0500191
192 self.hostname = hostname
193 self.username = username
194 self.password = password
George Keishing7a61aa22023-06-26 13:18:37 +0530195 self.port_ssh = str(port_ssh)
George Keishinge8a41752023-06-22 21:42:47 +0530196 self.port_https = str(port_https)
197 self.port_ipmi = str(port_ipmi)
Peter D Phane86d9a52021-07-15 10:42:25 -0500198 self.ffdc_config = ffdc_config
199 self.location = location + "/" + remote_type.upper()
200 self.ssh_remoteclient = None
201 self.telnet_remoteclient = None
202 self.ffdc_dir_path = ""
203 self.ffdc_prefix = ""
204 self.target_type = remote_type.upper()
205 self.remote_protocol = remote_protocol.upper()
George Keishing04eb44e2025-05-16 22:14:14 +0530206 self.env_vars = env_vars if env_vars else {}
207 self.econfig = econfig if econfig else {}
Peter D Phane86d9a52021-07-15 10:42:25 -0500208 self.start_time = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600209 self.elapsed_time = ""
George Keishing04eb44e2025-05-16 22:14:14 +0530210 self.env_dict = {}
Peter D Phane86d9a52021-07-15 10:42:25 -0500211 self.logger = None
212
George Keishing04eb44e2025-05-16 22:14:14 +0530213 """
214 Set prefix values for SCP files and directories.
215 Since the time stamp is at second granularity, these values are set
216 here to be sure that all files for this run will have the same
217 timestamps and be saved in the same directory.
218 self.location == local system for now
219 """
Peter D Phan5e56f522021-12-20 13:19:41 -0600220 self.set_ffdc_default_store_path()
Peter D Phane86d9a52021-07-15 10:42:25 -0500221
Peter D Phan5e56f522021-12-20 13:19:41 -0600222 # Logger for this run. Need to be after set_ffdc_default_store_path()
Peter D Phane86d9a52021-07-15 10:42:25 -0500223 self.script_logging(getattr(logging, log_level.upper()))
224
225 # Verify top level directory exists for storage
226 self.validate_local_store(self.location)
227
Peter D Phan72ce6b82021-06-03 06:18:26 -0500228 if self.verify_script_env():
George Keishing04eb44e2025-05-16 22:14:14 +0530229 try:
230 with open(self.ffdc_config, "r") as file:
231 self.ffdc_actions = yaml.safe_load(file)
232 except yaml.YAMLError as e:
233 self.logger.error(e)
234 sys.exit(-1)
Peter D Phane86d9a52021-07-15 10:42:25 -0500235
George Keishing04eb44e2025-05-16 22:14:14 +0530236 if self.target_type not in self.ffdc_actions:
Peter D Phane86d9a52021-07-15 10:42:25 -0500237 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600238 "\n\tERROR: %s is not listed in %s.\n\n"
239 % (self.target_type, self.ffdc_config)
240 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500241 sys.exit(-1)
George Keishing04eb44e2025-05-16 22:14:14 +0530242
243 self.logger.info("\n\tENV: User define input YAML variables")
George Keishing7bc5ce32025-05-19 19:15:20 +0530244 self.load_env()
Peter D Phan72ce6b82021-06-03 06:18:26 -0500245 else:
Peter D Phan8462faf2021-06-16 12:24:15 -0500246 sys.exit(-1)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500247
248 def verify_script_env(self):
George Keishing967c1ed2025-05-17 16:32:41 +0530249 r"""
250 Verify that all required environment variables are set.
251
252 This method checks if all required environment variables are set.
253 If any required variable is missing, the method returns False.
254 Otherwise, it returns True.
255
256 Returns:
257 bool: True if all required environment variables are set,
258 False otherwise.
259 """
Peter D Phan72ce6b82021-06-03 06:18:26 -0500260 # Import to log version
261 import click
262 import paramiko
263
264 run_env_ok = True
Peter D Phan0c669772021-06-24 13:52:42 -0500265
George Keishingd805bc02025-02-28 12:17:13 +0530266 try:
267 redfishtool_version = (
268 self.run_tool_cmd("redfishtool -V").split(" ")[2].strip("\n")
269 )
270 except Exception as e:
271 self.logger.error("\tEXCEPTION redfishtool: %s", e)
272 redfishtool_version = "Not Installed (optional)"
273
274 try:
275 ipmitool_version = self.run_tool_cmd("ipmitool -V").split(" ")[2]
276 except Exception as e:
277 self.logger.error("\tEXCEPTION ipmitool: %s", e)
278 ipmitool_version = "Not Installed (optional)"
Peter D Phan0c669772021-06-24 13:52:42 -0500279
Peter D Phane86d9a52021-07-15 10:42:25 -0500280 self.logger.info("\n\t---- Script host environment ----")
Patrick Williams20f38712022-12-08 06:18:26 -0600281 self.logger.info(
282 "\t{:<10} {:<10}".format("Script hostname", os.uname()[1])
283 )
284 self.logger.info(
285 "\t{:<10} {:<10}".format("Script host os", platform.platform())
286 )
287 self.logger.info(
288 "\t{:<10} {:>10}".format("Python", platform.python_version())
289 )
290 self.logger.info("\t{:<10} {:>10}".format("PyYAML", yaml.__version__))
291 self.logger.info("\t{:<10} {:>10}".format("click", click.__version__))
292 self.logger.info(
293 "\t{:<10} {:>10}".format("paramiko", paramiko.__version__)
294 )
295 self.logger.info(
296 "\t{:<10} {:>9}".format("redfishtool", redfishtool_version)
297 )
298 self.logger.info(
299 "\t{:<10} {:>12}".format("ipmitool", ipmitool_version)
300 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500301
Patrick Williams20f38712022-12-08 06:18:26 -0600302 if eval(yaml.__version__.replace(".", ",")) < (5, 3, 0):
303 self.logger.error(
304 "\n\tERROR: Python or python packages do not meet minimum"
305 " version requirement."
306 )
307 self.logger.error(
308 "\tERROR: PyYAML version 5.3.0 or higher is needed.\n"
309 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500310 run_env_ok = False
311
Peter D Phane86d9a52021-07-15 10:42:25 -0500312 self.logger.info("\t---- End script host environment ----")
Peter D Phan72ce6b82021-06-03 06:18:26 -0500313 return run_env_ok
314
Patrick Williams20f38712022-12-08 06:18:26 -0600315 def script_logging(self, log_level_attr):
George Keishing967c1ed2025-05-17 16:32:41 +0530316 """
317 Create a logger for the script with the specified log level.
Peter D Phane86d9a52021-07-15 10:42:25 -0500318
George Keishing967c1ed2025-05-17 16:32:41 +0530319 This method creates a logger for the script with the specified
320 log level. The logger is configured to write log messages to a file
321 and the console.
322
323 self.logger = logging.getLogger(__name__)
324
325 Setting logger with __name__ will add the trace
326 Example:
327
328 INFO:ffdc_collector: System Type: OPENBMC
329
330 Currently, set to empty purposely to log as
331 System Type: OPENBMC
332
333 Parameters:
334 log_level_attr (str): The log level for the logger
335 (e.g., "DEBUG", "INFO", "WARNING",
336 "ERROR", "CRITICAL").
337
338 Returns:
339 None
Peter D Phane86d9a52021-07-15 10:42:25 -0500340 """
341 self.logger = logging.getLogger()
342 self.logger.setLevel(log_level_attr)
George Keishing967c1ed2025-05-17 16:32:41 +0530343
Patrick Williams20f38712022-12-08 06:18:26 -0600344 log_file_handler = logging.FileHandler(
345 self.ffdc_dir_path + "collector.log"
346 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500347 stdout_handler = logging.StreamHandler(sys.stdout)
George Keishing967c1ed2025-05-17 16:32:41 +0530348
Peter D Phane86d9a52021-07-15 10:42:25 -0500349 self.logger.addHandler(log_file_handler)
350 self.logger.addHandler(stdout_handler)
351
352 # Turn off paramiko INFO logging
353 logging.getLogger("paramiko").setLevel(logging.WARNING)
354
Peter D Phan72ce6b82021-06-03 06:18:26 -0500355 def target_is_pingable(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500356 r"""
George Keishing967c1ed2025-05-17 16:32:41 +0530357 Check if the target system is ping-able.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500358
George Keishing967c1ed2025-05-17 16:32:41 +0530359 This method checks if the target system is reachable by sending an
360 ICMP echo request (ping). If the target system responds to the ping,
361 the method returns True. Otherwise, it returns False.
362
363 Returns:
364 bool: True if the target system is ping-able, False otherwise.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500365 """
George Keishing967c1ed2025-05-17 16:32:41 +0530366 response = os.system("ping -c 2 %s 2>&1 >/dev/null" % self.hostname)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500367 if response == 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600368 self.logger.info(
369 "\n\t[Check] %s is ping-able.\t\t [OK]" % self.hostname
370 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500371 return True
372 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500373 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600374 "\n\tERROR: %s is not ping-able. FFDC collection aborted.\n"
375 % self.hostname
376 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500377 sys.exit(-1)
George Keishing967c1ed2025-05-17 16:32:41 +0530378 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500379
Peter D Phan72ce6b82021-06-03 06:18:26 -0500380 def collect_ffdc(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500381 r"""
George Keishing967c1ed2025-05-17 16:32:41 +0530382 Initiate FFDC collection based on the requested protocol.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500383
George Keishing967c1ed2025-05-17 16:32:41 +0530384 This method initiates FFDC (First Failure Data Capture) collection
385 based on the requested protocol (SSH,SCP, TELNET, REDFISH, IPMI).
386 The method establishes a connection to the target system using the
387 specified protocol and collects the required FFDC data.
388
389 Returns:
390 None
Peter D Phan72ce6b82021-06-03 06:18:26 -0500391 """
Patrick Williams20f38712022-12-08 06:18:26 -0600392 self.logger.info(
393 "\n\t---- Start communicating with %s ----" % self.hostname
394 )
Peter D Phan7610bc42021-07-06 06:31:05 -0500395 self.start_time = time.time()
Peter D Phan0c669772021-06-24 13:52:42 -0500396
George Keishingf5a57502021-07-22 16:43:47 -0500397 # Find the list of target and protocol supported.
398 check_protocol_list = []
399 config_dict = self.ffdc_actions
Peter D Phan0c669772021-06-24 13:52:42 -0500400
George Keishingf5a57502021-07-22 16:43:47 -0500401 for target_type in config_dict.keys():
402 if self.target_type != target_type:
403 continue
George Keishingeafba182021-06-29 13:44:58 -0500404
George Keishingf5a57502021-07-22 16:43:47 -0500405 for k, v in config_dict[target_type].items():
George Keishing967c1ed2025-05-17 16:32:41 +0530406 if v["PROTOCOL"][0] not in check_protocol_list:
407 check_protocol_list.append(v["PROTOCOL"][0])
Peter D Phanbff617a2021-07-22 08:41:35 -0500408
Patrick Williams20f38712022-12-08 06:18:26 -0600409 self.logger.info(
410 "\n\t %s protocol type: %s"
411 % (self.target_type, check_protocol_list)
412 )
Peter D Phanbff617a2021-07-22 08:41:35 -0500413
George Keishingf5a57502021-07-22 16:43:47 -0500414 verified_working_protocol = self.verify_protocol(check_protocol_list)
Peter D Phanbff617a2021-07-22 08:41:35 -0500415
George Keishingf5a57502021-07-22 16:43:47 -0500416 if verified_working_protocol:
Patrick Williams20f38712022-12-08 06:18:26 -0600417 self.logger.info(
418 "\n\t---- Completed protocol pre-requisite check ----\n"
419 )
Peter D Phan0c669772021-06-24 13:52:42 -0500420
George Keishingf5a57502021-07-22 16:43:47 -0500421 # Verify top level directory exists for storage
422 self.validate_local_store(self.location)
423
Patrick Williams20f38712022-12-08 06:18:26 -0600424 if (self.remote_protocol not in verified_working_protocol) and (
425 self.remote_protocol != "ALL"
426 ):
427 self.logger.info(
428 "\n\tWorking protocol list: %s" % verified_working_protocol
429 )
George Keishingf5a57502021-07-22 16:43:47 -0500430 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600431 "\tERROR: Requested protocol %s is not in working protocol"
George Keishing7899a452023-02-15 02:46:54 -0600432 " list.\n" % self.remote_protocol
Patrick Williams20f38712022-12-08 06:18:26 -0600433 )
George Keishingf5a57502021-07-22 16:43:47 -0500434 sys.exit(-1)
435 else:
436 self.generate_ffdc(verified_working_protocol)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500437
438 def ssh_to_target_system(self):
439 r"""
George Keishing04416092025-05-17 18:47:10 +0530440 Establish an SSH connection to the target system.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500441
George Keishing04416092025-05-17 18:47:10 +0530442 This method establishes an SSH connection to the target system using
443 the provided hostname, username, password, and SSH port. If the
444 connection is successful, the method returns True. Otherwise, it logs
445 an error message and returns False.
446
447 Returns:
448 bool: True if the connection is successful, False otherwise.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500449 """
450
Patrick Williams20f38712022-12-08 06:18:26 -0600451 self.ssh_remoteclient = SSHRemoteclient(
George Keishing7a61aa22023-06-26 13:18:37 +0530452 self.hostname, self.username, self.password, self.port_ssh
Patrick Williams20f38712022-12-08 06:18:26 -0600453 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500454
Peter D Phan5963d632021-07-12 09:58:55 -0500455 if self.ssh_remoteclient.ssh_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600456 self.logger.info(
457 "\n\t[Check] %s SSH connection established.\t [OK]"
458 % self.hostname
459 )
Peter D Phan733df632021-06-17 13:13:36 -0500460
Peter D Phan5963d632021-07-12 09:58:55 -0500461 # Check scp connection.
462 # If scp connection fails,
463 # continue with FFDC generation but skip scp files to local host.
464 self.ssh_remoteclient.scp_connection()
465 return True
466 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600467 self.logger.info(
468 "\n\t[Check] %s SSH connection.\t [NOT AVAILABLE]"
469 % self.hostname
470 )
Peter D Phan5963d632021-07-12 09:58:55 -0500471 return False
472
473 def telnet_to_target_system(self):
474 r"""
George Keishing04416092025-05-17 18:47:10 +0530475 Establish a Telnet connection to the target system.
476
477 This method establishes a Telnet connection to the target system using
478 the provided hostname, username, and Telnet port. If the connection is
479 successful, the method returns True. Otherwise, it logs an error
480 message and returns False.
481
482 Returns:
483 bool: True if the connection is successful, False otherwise.
Peter D Phan5963d632021-07-12 09:58:55 -0500484 """
Patrick Williams20f38712022-12-08 06:18:26 -0600485 self.telnet_remoteclient = TelnetRemoteclient(
486 self.hostname, self.username, self.password
487 )
Peter D Phan5963d632021-07-12 09:58:55 -0500488 if self.telnet_remoteclient.tn_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600489 self.logger.info(
490 "\n\t[Check] %s Telnet connection established.\t [OK]"
491 % self.hostname
492 )
Peter D Phan5963d632021-07-12 09:58:55 -0500493 return True
494 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600495 self.logger.info(
496 "\n\t[Check] %s Telnet connection.\t [NOT AVAILABLE]"
497 % self.hostname
498 )
Peter D Phan5963d632021-07-12 09:58:55 -0500499 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500500
George Keishing772c9772021-06-16 23:23:42 -0500501 def generate_ffdc(self, working_protocol_list):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500502 r"""
George Keishing04416092025-05-17 18:47:10 +0530503 Generate FFDC (First Failure Data Capture) based on the remote host
504 type and working protocols.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500505
George Keishing04416092025-05-17 18:47:10 +0530506 This method determines the actions to be performed for generating FFDC
507 based on the remote host type and the list of confirmed working
508 protocols. The method iterates through the available actions for the
509 remote host type and checks if any of the working protocols are
510 supported. If a supported protocol is found, the method executes the
511 corresponding FFDC generation action.
512
513 Parameters:
514 working_protocol_list (list): A list of confirmed working
515 protocols to connect to the
516 remote host.
517
518 Returns:
519 None
Peter D Phan72ce6b82021-06-03 06:18:26 -0500520 """
Patrick Williams20f38712022-12-08 06:18:26 -0600521 self.logger.info(
522 "\n\t---- Executing commands on " + self.hostname + " ----"
523 )
524 self.logger.info(
525 "\n\tWorking protocol list: %s" % working_protocol_list
526 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500527
George Keishingf5a57502021-07-22 16:43:47 -0500528 config_dict = self.ffdc_actions
529 for target_type in config_dict.keys():
530 if self.target_type != target_type:
George Keishing6ea92b02021-07-01 11:20:50 -0500531 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500532
Peter D Phane86d9a52021-07-15 10:42:25 -0500533 self.logger.info("\n\tFFDC Path: %s " % self.ffdc_dir_path)
Patrick Williams20f38712022-12-08 06:18:26 -0600534 global_plugin_dict["global_log_store_path"] = self.ffdc_dir_path
George Keishingf5a57502021-07-22 16:43:47 -0500535 self.logger.info("\tSystem Type: %s" % target_type)
536 for k, v in config_dict[target_type].items():
George Keishing04416092025-05-17 18:47:10 +0530537 protocol = v["PROTOCOL"][0]
538
Patrick Williams20f38712022-12-08 06:18:26 -0600539 if (
540 self.remote_protocol not in working_protocol_list
541 and self.remote_protocol != "ALL"
George Keishing04416092025-05-17 18:47:10 +0530542 ) or protocol not in working_protocol_list:
George Keishingf5a57502021-07-22 16:43:47 -0500543 continue
544
George Keishingb7607612021-07-27 13:31:23 -0500545 if protocol in working_protocol_list:
George Keishing04416092025-05-17 18:47:10 +0530546 if protocol in ["SSH", "SCP"]:
George Keishing12fd0652021-07-27 13:57:11 -0500547 self.protocol_ssh(protocol, target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600548 elif protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -0500549 self.protocol_telnet(target_type, k)
George Keishing04416092025-05-17 18:47:10 +0530550 elif protocol in ["REDFISH", "IPMI", "SHELL"]:
551 self.protocol_service_execute(protocol, target_type, k)
George Keishingb7607612021-07-27 13:31:23 -0500552 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600553 self.logger.error(
554 "\n\tERROR: %s is not available for %s."
555 % (protocol, self.hostname)
556 )
George Keishingeafba182021-06-29 13:44:58 -0500557
Peter D Phan04aca3b2021-06-21 10:37:18 -0500558 # Close network connection after collecting all files
Patrick Williams20f38712022-12-08 06:18:26 -0600559 self.elapsed_time = time.strftime(
560 "%H:%M:%S", time.gmtime(time.time() - self.start_time)
561 )
George Keishing48972ba2025-05-05 17:40:29 +0530562 self.logger.info("\n\tTotal time taken: %s" % self.elapsed_time)
Peter D Phanbff617a2021-07-22 08:41:35 -0500563 if self.ssh_remoteclient:
564 self.ssh_remoteclient.ssh_remoteclient_disconnect()
565 if self.telnet_remoteclient:
566 self.telnet_remoteclient.tn_remoteclient_disconnect()
Peter D Phan04aca3b2021-06-21 10:37:18 -0500567
Patrick Williams20f38712022-12-08 06:18:26 -0600568 def protocol_ssh(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500569 r"""
570 Perform actions using SSH and SCP protocols.
571
George Keishing04416092025-05-17 18:47:10 +0530572 This method executes a set of commands using the SSH protocol to
573 connect to the target system and collect FFDC data. The method takes
574 the protocol, target type, and sub-type as arguments and performs the
575 corresponding actions based on the provided parameters.
Peter D Phan0c669772021-06-24 13:52:42 -0500576
George Keishing04416092025-05-17 18:47:10 +0530577 Parameters:
578 protocol (str): The protocol to execute (SSH or SCP).
579 target_type (str): The type group of the remote host.
580 sub_type (str): The group type of commands to execute.
581
582 Returns:
583 None
584 """
Patrick Williams20f38712022-12-08 06:18:26 -0600585 if protocol == "SCP":
George Keishingf5a57502021-07-22 16:43:47 -0500586 self.group_copy(self.ffdc_actions[target_type][sub_type])
George Keishing6ea92b02021-07-01 11:20:50 -0500587 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600588 self.collect_and_copy_ffdc(
589 self.ffdc_actions[target_type][sub_type]
590 )
Peter D Phan0c669772021-06-24 13:52:42 -0500591
Patrick Williams20f38712022-12-08 06:18:26 -0600592 def protocol_telnet(self, target_type, sub_type):
Peter D Phan5963d632021-07-12 09:58:55 -0500593 r"""
George Keishing04416092025-05-17 18:47:10 +0530594 Perform actions using the Telnet protocol.
595
596 This method executes a set of commands using the Telnet protocol to
597 connect to the target system and collect FFDC data. The method takes
598 the target type and sub-type as arguments and performs the
599 corresponding actions based on the provided parameters.
600
601 Parameters:
602 target_type (str): The type group of the remote host.
603 sub_type (str): The group type of commands to execute.
604
605 Returns:
606 None
Peter D Phan5963d632021-07-12 09:58:55 -0500607 """
Patrick Williams20f38712022-12-08 06:18:26 -0600608 self.logger.info(
609 "\n\t[Run] Executing commands on %s using %s"
610 % (self.hostname, "TELNET")
611 )
Peter D Phan5963d632021-07-12 09:58:55 -0500612 telnet_files_saved = []
613 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600614 list_of_commands = self.ffdc_actions[target_type][sub_type]["COMMANDS"]
Peter D Phan5963d632021-07-12 09:58:55 -0500615 for index, each_cmd in enumerate(list_of_commands, start=0):
616 command_txt, command_timeout = self.unpack_command(each_cmd)
Patrick Williams20f38712022-12-08 06:18:26 -0600617 result = self.telnet_remoteclient.execute_command(
618 command_txt, command_timeout
619 )
Peter D Phan5963d632021-07-12 09:58:55 -0500620 if result:
621 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600622 targ_file = self.ffdc_actions[target_type][sub_type][
623 "FILES"
624 ][index]
Peter D Phan5963d632021-07-12 09:58:55 -0500625 except IndexError:
Peter D Phane86d9a52021-07-15 10:42:25 -0500626 targ_file = command_txt
627 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600628 "\n\t[WARN] Missing filename to store data from"
629 " telnet %s." % each_cmd
630 )
631 self.logger.warning(
632 "\t[WARN] Data will be stored in %s." % targ_file
633 )
634 targ_file_with_path = (
635 self.ffdc_dir_path + self.ffdc_prefix + targ_file
636 )
Peter D Phan5963d632021-07-12 09:58:55 -0500637 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600638 with open(targ_file_with_path, "w") as fp:
Peter D Phan5963d632021-07-12 09:58:55 -0500639 fp.write(result)
640 fp.close
641 telnet_files_saved.append(targ_file)
642 progress_counter += 1
643 self.print_progress(progress_counter)
Peter D Phane86d9a52021-07-15 10:42:25 -0500644 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan5963d632021-07-12 09:58:55 -0500645 for file in telnet_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500646 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
Peter D Phan5963d632021-07-12 09:58:55 -0500647
George Keishing04416092025-05-17 18:47:10 +0530648 def protocol_service_execute(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500649 r"""
George Keishing506b0582021-07-27 09:31:22 -0500650 Perform actions for a given protocol.
Peter D Phan0c669772021-06-24 13:52:42 -0500651
George Keishing04416092025-05-17 18:47:10 +0530652 This method executes a set of commands using the specified protocol to
653 connect to the target system and collect FFDC data. The method takes
654 the protocol, target type, and sub-type as arguments and performs the
655 corresponding actions based on the provided parameters.
Peter D Phan0c669772021-06-24 13:52:42 -0500656
George Keishing04416092025-05-17 18:47:10 +0530657 Parameters:
658 protocol (str): The protocol to execute
659 (REDFISH, IPMI, or SHELL).
660 target_type (str): The type group of the remote host.
661 sub_type (str): The group type of commands to execute.
662
663 Returns:
664 None
665 """
Patrick Williams20f38712022-12-08 06:18:26 -0600666 self.logger.info(
667 "\n\t[Run] Executing commands to %s using %s"
668 % (self.hostname, protocol)
669 )
George Keishing506b0582021-07-27 09:31:22 -0500670 executed_files_saved = []
George Keishingeafba182021-06-29 13:44:58 -0500671 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600672 list_of_cmd = self.get_command_list(
673 self.ffdc_actions[target_type][sub_type]
674 )
George Keishingeafba182021-06-29 13:44:58 -0500675 for index, each_cmd in enumerate(list_of_cmd, start=0):
George Keishingcaa97e62021-08-03 14:00:09 -0500676 plugin_call = False
George Keishingb97a9042021-07-29 07:41:20 -0500677 if isinstance(each_cmd, dict):
Patrick Williams20f38712022-12-08 06:18:26 -0600678 if "plugin" in each_cmd:
George Keishing1e7b0182021-08-06 14:05:54 -0500679 # If the error is set and plugin explicitly
680 # requested to skip execution on error..
George Keishing7bc5ce32025-05-19 19:15:20 +0530681 if global_plugin_error_dict[
Patrick Williams20f38712022-12-08 06:18:26 -0600682 "exit_on_error"
683 ] and self.plugin_error_check(each_cmd["plugin"]):
684 self.logger.info(
685 "\n\t[PLUGIN-ERROR] exit_on_error: %s"
George Keishing7bc5ce32025-05-19 19:15:20 +0530686 % global_plugin_error_dict["exit_on_error"]
Patrick Williams20f38712022-12-08 06:18:26 -0600687 )
688 self.logger.info(
689 "\t[PLUGIN-SKIP] %s" % each_cmd["plugin"][0]
690 )
George Keishing1e7b0182021-08-06 14:05:54 -0500691 continue
George Keishingcaa97e62021-08-03 14:00:09 -0500692 plugin_call = True
George Keishingb97a9042021-07-29 07:41:20 -0500693 # call the plugin
694 self.logger.info("\n\t[PLUGIN-START]")
Patrick Williams20f38712022-12-08 06:18:26 -0600695 result = self.execute_plugin_block(each_cmd["plugin"])
George Keishingb97a9042021-07-29 07:41:20 -0500696 self.logger.info("\t[PLUGIN-END]\n")
George Keishingb97a9042021-07-29 07:41:20 -0500697 else:
George Keishing2b83e042021-08-03 12:56:11 -0500698 each_cmd = self.yaml_env_and_plugin_vars_populate(each_cmd)
George Keishingb97a9042021-07-29 07:41:20 -0500699
George Keishingcaa97e62021-08-03 14:00:09 -0500700 if not plugin_call:
701 result = self.run_tool_cmd(each_cmd)
George Keishingeafba182021-06-29 13:44:58 -0500702 if result:
703 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600704 file_name = self.get_file_list(
705 self.ffdc_actions[target_type][sub_type]
706 )[index]
George Keishingb97a9042021-07-29 07:41:20 -0500707 # If file is specified as None.
George Keishing0581cb02021-08-05 15:08:58 -0500708 if file_name == "None":
George Keishingb97a9042021-07-29 07:41:20 -0500709 continue
Patrick Williams20f38712022-12-08 06:18:26 -0600710 targ_file = self.yaml_env_and_plugin_vars_populate(
711 file_name
712 )
George Keishingeafba182021-06-29 13:44:58 -0500713 except IndexError:
Patrick Williams20f38712022-12-08 06:18:26 -0600714 targ_file = each_cmd.split("/")[-1]
George Keishing506b0582021-07-27 09:31:22 -0500715 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600716 "\n\t[WARN] Missing filename to store data from %s."
717 % each_cmd
718 )
719 self.logger.warning(
720 "\t[WARN] Data will be stored in %s." % targ_file
721 )
George Keishingeafba182021-06-29 13:44:58 -0500722
Patrick Williams20f38712022-12-08 06:18:26 -0600723 targ_file_with_path = (
724 self.ffdc_dir_path + self.ffdc_prefix + targ_file
725 )
George Keishingeafba182021-06-29 13:44:58 -0500726
727 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600728 with open(targ_file_with_path, "w") as fp:
George Keishing91308ea2021-08-10 14:43:15 -0500729 if isinstance(result, dict):
730 fp.write(json.dumps(result))
731 else:
732 fp.write(result)
George Keishingeafba182021-06-29 13:44:58 -0500733 fp.close
George Keishing506b0582021-07-27 09:31:22 -0500734 executed_files_saved.append(targ_file)
George Keishingeafba182021-06-29 13:44:58 -0500735
736 progress_counter += 1
737 self.print_progress(progress_counter)
738
Peter D Phane86d9a52021-07-15 10:42:25 -0500739 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
George Keishingeafba182021-06-29 13:44:58 -0500740
George Keishing506b0582021-07-27 09:31:22 -0500741 for file in executed_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500742 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
George Keishingeafba182021-06-29 13:44:58 -0500743
Patrick Williams20f38712022-12-08 06:18:26 -0600744 def collect_and_copy_ffdc(
745 self, ffdc_actions_for_target_type, form_filename=False
746 ):
Peter D Phan04aca3b2021-06-21 10:37:18 -0500747 r"""
George Keishing04416092025-05-17 18:47:10 +0530748 Send commands and collect FFDC data from the targeted system.
Peter D Phan04aca3b2021-06-21 10:37:18 -0500749
George Keishing04416092025-05-17 18:47:10 +0530750 This method sends a set of commands and collects FFDC data from the
751 targeted system based on the provided ffdc_actions_for_target_type
752 dictionary. The method also has an optional form_filename parameter,
753 which, if set to True, prepends the target type to the output file
754 name.
755
756 Parameters:
757 ffdc_actions_for_target_type (dict): A dictionary containing
758 commands and files for the
759 selected remote host type.
760 form_filename (bool, optional): If True, prepends the target
761 type to the output file name.
762 Defaults to False.
763
764 Returns:
765 None
Peter D Phan04aca3b2021-06-21 10:37:18 -0500766 """
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500767 # Executing commands, if any
Patrick Williams20f38712022-12-08 06:18:26 -0600768 self.ssh_execute_ffdc_commands(
769 ffdc_actions_for_target_type, form_filename
770 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500771
Peter D Phan3beb02e2021-07-06 13:25:17 -0500772 # Copying files
Peter D Phan5963d632021-07-12 09:58:55 -0500773 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600774 self.logger.info(
775 "\n\n\tCopying FFDC files from remote system %s.\n"
776 % self.hostname
777 )
Peter D Phan2b8052d2021-06-22 10:55:41 -0500778
Peter D Phan04aca3b2021-06-21 10:37:18 -0500779 # Retrieving files from target system
George Keishingf5a57502021-07-22 16:43:47 -0500780 list_of_files = self.get_file_list(ffdc_actions_for_target_type)
Patrick Williams20f38712022-12-08 06:18:26 -0600781 self.scp_ffdc(
782 self.ffdc_dir_path,
783 self.ffdc_prefix,
784 form_filename,
785 list_of_files,
786 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500787 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600788 self.logger.info(
789 "\n\n\tSkip copying FFDC files from remote system %s.\n"
790 % self.hostname
791 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500792
Patrick Williams20f38712022-12-08 06:18:26 -0600793 def get_command_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500794 r"""
George Keishing04416092025-05-17 18:47:10 +0530795 Fetch a list of commands from the configuration file.
Peter D Phanbabf2962021-07-07 11:24:40 -0500796
George Keishing04416092025-05-17 18:47:10 +0530797 This method retrieves a list of commands from the
798 ffdc_actions_for_target_type dictionary, which contains commands and
799 files for the selected remote host type. The method returns the list
800 of commands.
801
802 Parameters:
803 ffdc_actions_for_target_type (dict): A dictionary containing
804 commands and files for the
805 selected remote host type.
806
807 Returns:
808 list: A list of commands.
Peter D Phanbabf2962021-07-07 11:24:40 -0500809 """
810 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600811 list_of_commands = ffdc_actions_for_target_type["COMMANDS"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500812 except KeyError:
813 list_of_commands = []
814 return list_of_commands
815
Patrick Williams20f38712022-12-08 06:18:26 -0600816 def get_file_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500817 r"""
George Keishing04416092025-05-17 18:47:10 +0530818 Fetch a list of files from the configuration file.
Peter D Phanbabf2962021-07-07 11:24:40 -0500819
George Keishing04416092025-05-17 18:47:10 +0530820 This method retrieves a list of files from the
821 ffdc_actions_for_target_type dictionary, which contains commands and
822 files for the selected remote host type. The method returns the list
823 of files.
824
825 Parameters:
826 ffdc_actions_for_target_type (dict): A dictionary containing
827 commands and files for the
828 selected remote host type.
829
830 Returns:
831 list: A list of files.
Peter D Phanbabf2962021-07-07 11:24:40 -0500832 """
833 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600834 list_of_files = ffdc_actions_for_target_type["FILES"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500835 except KeyError:
836 list_of_files = []
837 return list_of_files
838
Patrick Williams20f38712022-12-08 06:18:26 -0600839 def unpack_command(self, command):
Peter D Phan5963d632021-07-12 09:58:55 -0500840 r"""
George Keishing04416092025-05-17 18:47:10 +0530841 Unpack a command from the configuration file, handling both dictionary
842 and string inputs.
Peter D Phan5963d632021-07-12 09:58:55 -0500843
George Keishing04416092025-05-17 18:47:10 +0530844 This method takes a command from the configuration file, which can be
845 either a dictionary or a string. If the input is a dictionary, the
846 method extracts the command text and timeout from the dictionary.
847 If the input is a string, the method assumes a default timeout of
848 60 seconds.
849 The method returns a tuple containing the command text and timeout.
850
851 Parameters:
852 command (dict or str): A command from the configuration file,
853 which can be either a dictionary or a
854 string.
855
856 Returns:
857 tuple: A tuple containing the command text and timeout.
Peter D Phan5963d632021-07-12 09:58:55 -0500858 """
859 if isinstance(command, dict):
860 command_txt = next(iter(command))
861 command_timeout = next(iter(command.values()))
862 elif isinstance(command, str):
863 command_txt = command
864 # Default command timeout 60 seconds
865 command_timeout = 60
866
867 return command_txt, command_timeout
868
Patrick Williams20f38712022-12-08 06:18:26 -0600869 def ssh_execute_ffdc_commands(
870 self, ffdc_actions_for_target_type, form_filename=False
871 ):
Peter D Phan3beb02e2021-07-06 13:25:17 -0500872 r"""
George Keishing04416092025-05-17 18:47:10 +0530873 Send commands in the ffdc_config file to the targeted system using SSH.
Peter D Phan3beb02e2021-07-06 13:25:17 -0500874
George Keishing04416092025-05-17 18:47:10 +0530875 This method sends a set of commands and collects FFDC data from the
876 targeted system using the SSH protocol. The method takes the
877 ffdc_actions_for_target_type dictionary and an optional
878 form_filename parameter as arguments.
879
880 If form_filename is set to True, the method prepends the target type
881 to the output file name. The method returns the output of the executed
882 commands.
883
884 It also prints the progress counter string + on the console.
885
886 Parameters:
887 ffdc_actions_for_target_type (dict): A dictionary containing
888 commands and files for the
889 selected remote host type.
890 form_filename (bool, optional): If True, prepends the target
891 type to the output file name.
892 Defaults to False.
893
894 Returns:
895 None
Peter D Phan3beb02e2021-07-06 13:25:17 -0500896 """
Patrick Williams20f38712022-12-08 06:18:26 -0600897 self.logger.info(
898 "\n\t[Run] Executing commands on %s using %s"
899 % (self.hostname, ffdc_actions_for_target_type["PROTOCOL"][0])
900 )
Peter D Phan3beb02e2021-07-06 13:25:17 -0500901
George Keishingf5a57502021-07-22 16:43:47 -0500902 list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500903 # If command list is empty, returns
904 if not list_of_commands:
905 return
906
907 progress_counter = 0
908 for command in list_of_commands:
Peter D Phan5963d632021-07-12 09:58:55 -0500909 command_txt, command_timeout = self.unpack_command(command)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500910
911 if form_filename:
912 command_txt = str(command_txt % self.target_type)
913
Patrick Williams20f38712022-12-08 06:18:26 -0600914 (
915 cmd_exit_code,
916 err,
917 response,
918 ) = self.ssh_remoteclient.execute_command(
919 command_txt, command_timeout
920 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500921
922 if cmd_exit_code:
923 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600924 "\n\t\t[WARN] %s exits with code %s."
925 % (command_txt, str(cmd_exit_code))
926 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500927 self.logger.warning("\t\t[WARN] %s " % err)
Peter D Phanbabf2962021-07-07 11:24:40 -0500928
Peter D Phan3beb02e2021-07-06 13:25:17 -0500929 progress_counter += 1
930 self.print_progress(progress_counter)
931
Peter D Phane86d9a52021-07-15 10:42:25 -0500932 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan3beb02e2021-07-06 13:25:17 -0500933
Patrick Williams20f38712022-12-08 06:18:26 -0600934 def group_copy(self, ffdc_actions_for_target_type):
Peter D Phan56429a62021-06-23 08:38:29 -0500935 r"""
George Keishing04416092025-05-17 18:47:10 +0530936 SCP a group of files (wildcard) from the remote host.
Peter D Phan56429a62021-06-23 08:38:29 -0500937
George Keishing04416092025-05-17 18:47:10 +0530938 This method copies a group of files from the remote host using the SCP
939 protocol. The method takes the fdc_actions_for_target_type dictionary
940 as an argument, which contains commands and files for the selected
941 remote host type.
942
943 Parameters:
944 fdc_actions_for_target_type (dict): A dictionary containing
945 commands and files for the
946 selected remote host type.
947
948 Returns:
949 None
Peter D Phan56429a62021-06-23 08:38:29 -0500950 """
Peter D Phan5963d632021-07-12 09:58:55 -0500951 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600952 self.logger.info(
953 "\n\tCopying files from remote system %s via SCP.\n"
954 % self.hostname
955 )
Peter D Phan56429a62021-06-23 08:38:29 -0500956
Patrick Williams20f38712022-12-08 06:18:26 -0600957 list_of_commands = self.get_command_list(
958 ffdc_actions_for_target_type
959 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500960 # If command list is empty, returns
961 if not list_of_commands:
962 return
Peter D Phan56429a62021-06-23 08:38:29 -0500963
Peter D Phanbabf2962021-07-07 11:24:40 -0500964 for command in list_of_commands:
965 try:
George Keishingb4540e72021-08-02 13:48:46 -0500966 command = self.yaml_env_and_plugin_vars_populate(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500967 except IndexError:
George Keishingb4540e72021-08-02 13:48:46 -0500968 self.logger.error("\t\tInvalid command %s" % command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500969 continue
970
Patrick Williams20f38712022-12-08 06:18:26 -0600971 (
972 cmd_exit_code,
973 err,
974 response,
975 ) = self.ssh_remoteclient.execute_command(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500976
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500977 # If file does not exist, code take no action.
978 # cmd_exit_code is ignored for this scenario.
Peter D Phan56429a62021-06-23 08:38:29 -0500979 if response:
Patrick Williams20f38712022-12-08 06:18:26 -0600980 scp_result = self.ssh_remoteclient.scp_file_from_remote(
981 response.split("\n"), self.ffdc_dir_path
982 )
Peter D Phan56429a62021-06-23 08:38:29 -0500983 if scp_result:
Patrick Williams20f38712022-12-08 06:18:26 -0600984 self.logger.info(
985 "\t\tSuccessfully copied from "
986 + self.hostname
987 + ":"
988 + command
989 )
Peter D Phan56429a62021-06-23 08:38:29 -0500990 else:
George Keishinga56e87b2021-08-06 00:24:19 -0500991 self.logger.info("\t\t%s has no result" % command)
Peter D Phan56429a62021-06-23 08:38:29 -0500992
993 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600994 self.logger.info(
995 "\n\n\tSkip copying files from remote system %s.\n"
996 % self.hostname
997 )
Peter D Phan56429a62021-06-23 08:38:29 -0500998
Patrick Williams20f38712022-12-08 06:18:26 -0600999 def scp_ffdc(
1000 self,
1001 targ_dir_path,
1002 targ_file_prefix,
1003 form_filename,
1004 file_list=None,
1005 quiet=None,
1006 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -05001007 r"""
George Keishing04416092025-05-17 18:47:10 +05301008 SCP all files in the file_dict to the indicated directory on the local
George Keishingc754b432025-04-24 14:27:14 +05301009 system.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001010
George Keishing04416092025-05-17 18:47:10 +05301011 This method copies all files specified in the file_dict dictionary
1012 from the targeted system to the local system using the SCP protocol.
1013 The method takes the target directory path, target file prefix, and a
1014 boolean flag form_filename as required arguments.
1015
1016 The file_dict argument is optional and contains the files to be copied.
1017 The quiet argument is also optional and, if set to True, suppresses
1018 the output of the SCP operation.
1019
1020 Parameters:
1021 targ_dir_path (str): The path of the directory to receive
1022 the files on the local system.
1023 targ_file_prefix (str): Prefix which will be prepended to each
Peter D Phan72ce6b82021-06-03 06:18:26 -05001024 target file's name.
George Keishing04416092025-05-17 18:47:10 +05301025 form_filename (bool): If True, prepends the target type to
1026 the file names.
1027 file_dict (dict, optional): A dictionary containing the files to
1028 be copied. Defaults to None.
1029 quiet (bool, optional): If True, suppresses the output of the
1030 SCP operation. Defaults to None.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001031
George Keishing04416092025-05-17 18:47:10 +05301032 Returns:
1033 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001034 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001035 progress_counter = 0
1036 for filename in file_list:
Peter D Phan2b8052d2021-06-22 10:55:41 -05001037 if form_filename:
1038 filename = str(filename % self.target_type)
Peter D Phan72ce6b82021-06-03 06:18:26 -05001039 source_file_path = filename
Patrick Williams20f38712022-12-08 06:18:26 -06001040 targ_file_path = (
1041 targ_dir_path + targ_file_prefix + filename.split("/")[-1]
1042 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001043
Peter D Phanbabf2962021-07-07 11:24:40 -05001044 # If source file name contains wild card, copy filename as is.
Patrick Williams20f38712022-12-08 06:18:26 -06001045 if "*" in source_file_path:
1046 scp_result = self.ssh_remoteclient.scp_file_from_remote(
1047 source_file_path, self.ffdc_dir_path
1048 )
Peter D Phanbabf2962021-07-07 11:24:40 -05001049 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001050 scp_result = self.ssh_remoteclient.scp_file_from_remote(
1051 source_file_path, targ_file_path
1052 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001053
1054 if not quiet:
1055 if scp_result:
Peter D Phane86d9a52021-07-15 10:42:25 -05001056 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -06001057 "\t\tSuccessfully copied from "
1058 + self.hostname
1059 + ":"
1060 + source_file_path
1061 + ".\n"
1062 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001063 else:
Peter D Phane86d9a52021-07-15 10:42:25 -05001064 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -06001065 "\t\tFail to copy from "
1066 + self.hostname
1067 + ":"
1068 + source_file_path
1069 + ".\n"
1070 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001071 else:
1072 progress_counter += 1
1073 self.print_progress(progress_counter)
1074
Peter D Phan5e56f522021-12-20 13:19:41 -06001075 def set_ffdc_default_store_path(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -05001076 r"""
George Keishing04416092025-05-17 18:47:10 +05301077 Set default values for self.ffdc_dir_path and self.ffdc_prefix.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001078
George Keishing04416092025-05-17 18:47:10 +05301079 This method sets default values for the self.ffdc_dir_path and
1080 self.ffdc_prefix class variables.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001081
George Keishing04416092025-05-17 18:47:10 +05301082 The collected FFDC files will be stored in the directory
1083 /self.location/hostname_timestr/, with individual files having the
1084 format timestr_filename where timestr is in %Y%m%d-%H%M%S.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001085
George Keishing04416092025-05-17 18:47:10 +05301086 Returns:
1087 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001088 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001089 timestr = time.strftime("%Y%m%d-%H%M%S")
Patrick Williams20f38712022-12-08 06:18:26 -06001090 self.ffdc_dir_path = (
1091 self.location + "/" + self.hostname + "_" + timestr + "/"
1092 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001093 self.ffdc_prefix = timestr + "_"
1094 self.validate_local_store(self.ffdc_dir_path)
1095
Peter D Phan5e56f522021-12-20 13:19:41 -06001096 # Need to verify local store path exists prior to instantiate this class.
George Keishing04416092025-05-17 18:47:10 +05301097 # This class method to validate log path before referencing this class.
Peter D Phan5e56f522021-12-20 13:19:41 -06001098 @classmethod
1099 def validate_local_store(cls, dir_path):
Peter D Phan72ce6b82021-06-03 06:18:26 -05001100 r"""
George Keishing04416092025-05-17 18:47:10 +05301101 Ensure the specified directory exists to store FFDC files locally.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001102
George Keishing04416092025-05-17 18:47:10 +05301103 This method checks if the provided dir_path exists. If the directory
1104 does not exist, the method creates it. The method does not return any
1105 value.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001106
George Keishing04416092025-05-17 18:47:10 +05301107 Parameters:
1108 dir_path (str): The directory path where collected FFDC data files
1109 will be stored.
1110
1111 Returns:
1112 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001113 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001114 if not os.path.exists(dir_path):
1115 try:
George Keishing7b3a5132021-07-13 09:24:02 -05001116 os.makedirs(dir_path, 0o755)
Peter D Phan72ce6b82021-06-03 06:18:26 -05001117 except (IOError, OSError) as e:
1118 # PermissionError
1119 if e.errno == EPERM or e.errno == EACCES:
George Keishing15352052025-04-24 18:55:47 +05301120 print(
Patrick Williams20f38712022-12-08 06:18:26 -06001121 "\tERROR: os.makedirs %s failed with"
1122 " PermissionError.\n" % dir_path
1123 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001124 else:
George Keishing15352052025-04-24 18:55:47 +05301125 print(
Patrick Williams20f38712022-12-08 06:18:26 -06001126 "\tERROR: os.makedirs %s failed with %s.\n"
1127 % (dir_path, e.strerror)
1128 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001129 sys.exit(-1)
1130
1131 def print_progress(self, progress):
1132 r"""
George Keishing04416092025-05-17 18:47:10 +05301133 Print the current activity progress.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001134
George Keishing04416092025-05-17 18:47:10 +05301135 This method prints the current activity progress using the provided
1136 progress counter. The method does not return any value.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001137
George Keishing04416092025-05-17 18:47:10 +05301138 Parameters:
1139 progress (int): The current activity progress counter.
1140
1141 Returns:
1142 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001143 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001144 sys.stdout.write("\r\t" + "+" * progress)
1145 sys.stdout.flush()
Patrick Williams20f38712022-12-08 06:18:26 -06001146 time.sleep(0.1)
Peter D Phan0c669772021-06-24 13:52:42 -05001147
1148 def verify_redfish(self):
1149 r"""
George Keishing04416092025-05-17 18:47:10 +05301150 Verify if the remote host has the Redfish service active.
Peter D Phan0c669772021-06-24 13:52:42 -05001151
George Keishing04416092025-05-17 18:47:10 +05301152 This method checks if the remote host has the Redfish service active
1153 by sending a GET request to the Redfish base URL /redfish/v1/.
1154 If the request is successful (status code 200), the method returns
1155 stdout output of the run else error message.
1156
1157 Returns:
1158 str: Redfish service executed output.
Peter D Phan0c669772021-06-24 13:52:42 -05001159 """
Patrick Williams20f38712022-12-08 06:18:26 -06001160 redfish_parm = (
1161 "redfishtool -r "
1162 + self.hostname
George Keishing7a61aa22023-06-26 13:18:37 +05301163 + ":"
1164 + self.port_https
Patrick Williams20f38712022-12-08 06:18:26 -06001165 + " -S Always raw GET /redfish/v1/"
1166 )
1167 return self.run_tool_cmd(redfish_parm, True)
Peter D Phan0c669772021-06-24 13:52:42 -05001168
George Keishingeafba182021-06-29 13:44:58 -05001169 def verify_ipmi(self):
1170 r"""
George Keishing04416092025-05-17 18:47:10 +05301171 Verify if the remote host has the IPMI LAN service active.
George Keishingeafba182021-06-29 13:44:58 -05001172
George Keishing04416092025-05-17 18:47:10 +05301173 This method checks if the remote host has the IPMI LAN service active
1174 by sending an IPMI "power status" command.
1175
1176 If the command is successful (returns a non-empty response),
1177 else error message.
1178
1179 Returns:
1180 str: IPMI LAN service executed output.
George Keishingeafba182021-06-29 13:44:58 -05001181 """
Patrick Williams20f38712022-12-08 06:18:26 -06001182 if self.target_type == "OPENBMC":
1183 ipmi_parm = (
1184 "ipmitool -I lanplus -C 17 -U "
1185 + self.username
1186 + " -P "
1187 + self.password
1188 + " -H "
1189 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +05301190 + " -p "
1191 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -06001192 + " power status"
1193 )
George Keishing484f8242021-07-27 01:42:02 -05001194 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001195 ipmi_parm = (
1196 "ipmitool -I lanplus -P "
1197 + self.password
1198 + " -H "
1199 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +05301200 + " -p "
1201 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -06001202 + " power status"
1203 )
George Keishing484f8242021-07-27 01:42:02 -05001204
Patrick Williams20f38712022-12-08 06:18:26 -06001205 return self.run_tool_cmd(ipmi_parm, True)
George Keishingeafba182021-06-29 13:44:58 -05001206
Patrick Williams20f38712022-12-08 06:18:26 -06001207 def run_tool_cmd(self, parms_string, quiet=False):
George Keishingeafba182021-06-29 13:44:58 -05001208 r"""
George Keishing04416092025-05-17 18:47:10 +05301209 Run a CLI standard tool or script with the provided command options.
George Keishingeafba182021-06-29 13:44:58 -05001210
George Keishing04416092025-05-17 18:47:10 +05301211 This method runs a CLI standard tool or script with the provided
1212 parms_string command options. If the quiet parameter is set to True,
1213 the method suppresses the output of the command.
1214 The method returns the output of the command as a string.
1215
1216 Parameters:
1217 parms_string (str): The command options for the CLI tool or
1218 script.
1219 quiet (bool, optional): If True, suppresses the output of the
1220 command. Defaults to False.
1221
1222 Returns:
1223 str: The output of the command as a string.
George Keishingeafba182021-06-29 13:44:58 -05001224 """
1225
Patrick Williams20f38712022-12-08 06:18:26 -06001226 result = subprocess.run(
1227 [parms_string],
1228 stdout=subprocess.PIPE,
1229 stderr=subprocess.PIPE,
1230 shell=True,
1231 universal_newlines=True,
1232 )
George Keishingeafba182021-06-29 13:44:58 -05001233
1234 if result.stderr and not quiet:
George Keishing0e9b5ba2025-05-08 12:17:58 +05301235 if self.password in parms_string:
1236 parms_string = parms_string.replace(self.password, "********")
Patrick Williams20f38712022-12-08 06:18:26 -06001237 self.logger.error("\n\t\tERROR with %s " % parms_string)
1238 self.logger.error("\t\t" + result.stderr)
George Keishingeafba182021-06-29 13:44:58 -05001239
1240 return result.stdout
George Keishing04d29102021-07-16 02:05:57 -05001241
George Keishingf5a57502021-07-22 16:43:47 -05001242 def verify_protocol(self, protocol_list):
1243 r"""
George Keishing04416092025-05-17 18:47:10 +05301244 Perform a working check for the provided list of protocols.
George Keishingf5a57502021-07-22 16:43:47 -05001245
George Keishing04416092025-05-17 18:47:10 +05301246 This method checks if the specified protocols are available on the
1247 remote host. The method iterates through the protocol_list and
1248 attempts to establish a connection using each protocol.
1249
1250 If a connection is successfully established, the method append to the
1251 list and if any protocol fails to connect, the method ignores it.
1252
1253 Parameters:
1254 protocol_list (list): A list of protocols to check.
1255
1256 Returns:
1257 list: All protocols are available list.
George Keishingf5a57502021-07-22 16:43:47 -05001258 """
1259
1260 tmp_list = []
1261 if self.target_is_pingable():
1262 tmp_list.append("SHELL")
1263
1264 for protocol in protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -06001265 if self.remote_protocol != "ALL":
George Keishingf5a57502021-07-22 16:43:47 -05001266 if self.remote_protocol != protocol:
1267 continue
1268
1269 # Only check SSH/SCP once for both protocols
Patrick Williams20f38712022-12-08 06:18:26 -06001270 if (
1271 protocol == "SSH"
1272 or protocol == "SCP"
1273 and protocol not in tmp_list
1274 ):
George Keishingf5a57502021-07-22 16:43:47 -05001275 if self.ssh_to_target_system():
George Keishingaa638702021-07-26 11:48:28 -05001276 # Add only what user asked.
Patrick Williams20f38712022-12-08 06:18:26 -06001277 if self.remote_protocol != "ALL":
George Keishingaa638702021-07-26 11:48:28 -05001278 tmp_list.append(self.remote_protocol)
1279 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001280 tmp_list.append("SSH")
1281 tmp_list.append("SCP")
George Keishingf5a57502021-07-22 16:43:47 -05001282
Patrick Williams20f38712022-12-08 06:18:26 -06001283 if protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -05001284 if self.telnet_to_target_system():
1285 tmp_list.append(protocol)
1286
Patrick Williams20f38712022-12-08 06:18:26 -06001287 if protocol == "REDFISH":
George Keishingf5a57502021-07-22 16:43:47 -05001288 if self.verify_redfish():
1289 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001290 self.logger.info(
1291 "\n\t[Check] %s Redfish Service.\t\t [OK]"
1292 % self.hostname
1293 )
George Keishingf5a57502021-07-22 16:43:47 -05001294 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001295 self.logger.info(
1296 "\n\t[Check] %s Redfish Service.\t\t [NOT AVAILABLE]"
1297 % self.hostname
1298 )
George Keishingf5a57502021-07-22 16:43:47 -05001299
Patrick Williams20f38712022-12-08 06:18:26 -06001300 if protocol == "IPMI":
George Keishingf5a57502021-07-22 16:43:47 -05001301 if self.verify_ipmi():
1302 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001303 self.logger.info(
1304 "\n\t[Check] %s IPMI LAN Service.\t\t [OK]"
1305 % self.hostname
1306 )
George Keishingf5a57502021-07-22 16:43:47 -05001307 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001308 self.logger.info(
1309 "\n\t[Check] %s IPMI LAN Service.\t\t [NOT AVAILABLE]"
1310 % self.hostname
1311 )
George Keishingf5a57502021-07-22 16:43:47 -05001312
1313 return tmp_list
George Keishinge1686752021-07-27 12:55:28 -05001314
1315 def load_env(self):
1316 r"""
George Keishing0e9b5ba2025-05-08 12:17:58 +05301317 Load the user environment variables from a YAML file.
George Keishinge1686752021-07-27 12:55:28 -05001318
George Keishing0e9b5ba2025-05-08 12:17:58 +05301319 This method reads the environment variables from a YAML file specified
1320 in the ENV_FILE environment variable. If the file is not found or
1321 there is an error reading the file, an exception is raised.
1322
1323 The YAML file should have the following format:
1324
1325 .. code-block:: yaml
1326
1327 VAR_NAME: VAR_VALUE
1328
1329 Where VAR_NAME is the name of the environment variable, and
1330 VAR_VALUE is its value.
1331
1332 After loading the environment variables, they are stored in the
1333 self.env attribute for later use.
George Keishinge1686752021-07-27 12:55:28 -05001334 """
George Keishing0e9b5ba2025-05-08 12:17:58 +05301335
George Keishing7bc5ce32025-05-19 19:15:20 +05301336 tmp_env_vars = {
1337 "hostname": self.hostname,
1338 "username": self.username,
1339 "password": self.password,
1340 "port_ssh": self.port_ssh,
1341 "port_https": self.port_https,
1342 "port_ipmi": self.port_ipmi,
1343 }
George Keishinge1686752021-07-27 12:55:28 -05001344
George Keishing7bc5ce32025-05-19 19:15:20 +05301345 # Updatae default Env and Dict var for both so that it can be
1346 # verified when referencing it throughout the code.
1347 for key, value in tmp_env_vars.items():
1348 os.environ[key] = value
1349 self.env_dict[key] = value
George Keishinge1686752021-07-27 12:55:28 -05001350
1351 try:
1352 tmp_env_dict = {}
1353 if self.env_vars:
1354 tmp_env_dict = json.loads(self.env_vars)
1355 # Export ENV vars default.
1356 for key, value in tmp_env_dict.items():
1357 os.environ[key] = value
1358 self.env_dict[key] = str(value)
1359
George Keishing0e9b5ba2025-05-08 12:17:58 +05301360 # Load user specified ENV config YAML.
George Keishinge1686752021-07-27 12:55:28 -05001361 if self.econfig:
Patrick Williams20f38712022-12-08 06:18:26 -06001362 with open(self.econfig, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -05001363 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -07001364 tmp_env_dict = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -05001365 except yaml.YAMLError as e:
1366 self.logger.error(e)
1367 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001368 # Export ENV vars.
Patrick Williams20f38712022-12-08 06:18:26 -06001369 for key, value in tmp_env_dict["env_params"].items():
George Keishinge1686752021-07-27 12:55:28 -05001370 os.environ[key] = str(value)
1371 self.env_dict[key] = str(value)
1372 except json.decoder.JSONDecodeError as e:
1373 self.logger.error("\n\tERROR: %s " % e)
1374 sys.exit(-1)
George Keishing0e9b5ba2025-05-08 12:17:58 +05301375 except FileNotFoundError as e:
1376 self.logger.error("\n\tERROR: %s " % e)
1377 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001378
1379 # This to mask the password from displaying on the console.
1380 mask_dict = self.env_dict.copy()
1381 for k, v in mask_dict.items():
1382 if k.lower().find("password") != -1:
1383 hidden_text = []
1384 hidden_text.append(v)
Patrick Williams20f38712022-12-08 06:18:26 -06001385 password_regex = (
1386 "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1387 )
George Keishinge1686752021-07-27 12:55:28 -05001388 mask_dict[k] = re.sub(password_regex, "********", v)
1389
1390 self.logger.info(json.dumps(mask_dict, indent=8, sort_keys=False))
George Keishingb97a9042021-07-29 07:41:20 -05001391
George Keishingb97a9042021-07-29 07:41:20 -05001392 def execute_plugin_block(self, plugin_cmd_list):
1393 r"""
George Keishing04416092025-05-17 18:47:10 +05301394 Pack the plugin commands into qualified Python string objects.
George Keishingb97a9042021-07-29 07:41:20 -05001395
George Keishing04416092025-05-17 18:47:10 +05301396 This method processes the plugin_cmd_list argument, which is expected
1397 to contain a list of plugin commands read from a YAML file. The method
1398 iterates through the list, constructs a qualified Python string object
1399 for each plugin command, and returns a list of these string objects.
George Keishingb97a9042021-07-29 07:41:20 -05001400
George Keishing04416092025-05-17 18:47:10 +05301401 Parameters:
1402 plugin_cmd_list (list): A list of plugin commands containing
1403 plugin names and arguments.
1404 Plugin block read from YAML
1405 [
1406 {'plugin_name':'plugin.foo_func.my_func'},
1407 {'plugin_args':[10]},
1408 ]
George Keishingb97a9042021-07-29 07:41:20 -05001409
George Keishing04416092025-05-17 18:47:10 +05301410 Example:
1411 Execute and no return response
1412 - plugin:
1413 - plugin_name: plugin.foo_func.my_func
1414 - plugin_args:
1415 - arg1
1416 - arg2
George Keishingb97a9042021-07-29 07:41:20 -05001417
George Keishing04416092025-05-17 18:47:10 +05301418 Execute and return a response
1419 - plugin:
1420 - plugin_name: result = plugin.foo_func.my_func
1421 - plugin_args:
1422 - arg1
1423 - arg2
1424
1425 Execute and return multiple values response
1426 - plugin:
1427 - plugin_name: result1,result2 = plugin.foo_func.my_func
1428 - plugin_args:
1429 - arg1
1430 - arg2
1431
1432 Returns:
1433 str: Execute and not response or a string value(s) responses,
1434
George Keishingb97a9042021-07-29 07:41:20 -05001435 """
George Keishing7bc5ce32025-05-19 19:15:20 +05301436
1437 # Declare a variable plugin resp that can accept any data type.
1438 resp: Any = ""
1439 args_string = ""
1440
George Keishingb97a9042021-07-29 07:41:20 -05001441 try:
Patrick Williams20f38712022-12-08 06:18:26 -06001442 idx = self.key_index_list_dict("plugin_name", plugin_cmd_list)
George Keishing7bc5ce32025-05-19 19:15:20 +05301443 # Get plugin module name
Patrick Williams20f38712022-12-08 06:18:26 -06001444 plugin_name = plugin_cmd_list[idx]["plugin_name"]
George Keishing7bc5ce32025-05-19 19:15:20 +05301445
1446 # Get plugin function name
1447 idx = self.key_index_list_dict("plugin_function", plugin_cmd_list)
1448 plugin_function = plugin_cmd_list[idx]["plugin_function"]
1449
George Keishingb97a9042021-07-29 07:41:20 -05001450 # Equal separator means plugin function returns result.
George Keishing7bc5ce32025-05-19 19:15:20 +05301451 if " = " in plugin_function:
George Keishingb97a9042021-07-29 07:41:20 -05001452 # Ex. ['result', 'plugin.foo_func.my_func']
George Keishing7bc5ce32025-05-19 19:15:20 +05301453 plugin_function_args = plugin_function.split(" = ")
George Keishingb97a9042021-07-29 07:41:20 -05001454 # plugin func return data.
George Keishing7bc5ce32025-05-19 19:15:20 +05301455 for arg in plugin_function_args:
1456 if arg == plugin_function_args[-1]:
1457 plugin_function = arg
George Keishingb97a9042021-07-29 07:41:20 -05001458 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001459 plugin_resp = arg.split(",")
George Keishingb97a9042021-07-29 07:41:20 -05001460 # ['result1','result2']
1461 for x in plugin_resp:
1462 global_plugin_list.append(x)
1463 global_plugin_dict[x] = ""
1464
1465 # Walk the plugin args ['arg1,'arg2']
1466 # If the YAML plugin statement 'plugin_args' is not declared.
George Keishingf0eb1d62025-05-14 15:07:02 +05301467 plugin_args = []
Patrick Williams20f38712022-12-08 06:18:26 -06001468 if any("plugin_args" in d for d in plugin_cmd_list):
1469 idx = self.key_index_list_dict("plugin_args", plugin_cmd_list)
George Keishingf0eb1d62025-05-14 15:07:02 +05301470 if idx is not None:
1471 plugin_args = plugin_cmd_list[idx].get("plugin_args", [])
George Keishingb97a9042021-07-29 07:41:20 -05001472 plugin_args = self.yaml_args_populate(plugin_args)
1473 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301474 plugin_args = self.yaml_args_populate([])
George Keishingb97a9042021-07-29 07:41:20 -05001475
George Keishing7bc5ce32025-05-19 19:15:20 +05301476 # Replace keys in the string with their corresponding
1477 # values from the dictionary.
1478 for key, value in global_plugin_dict.items():
1479 # Iterate through the list and check if each element matched
1480 # exact or in the string. If matches update the plugin element
1481 # in the list.
1482 for index, element in enumerate(plugin_args):
1483 try:
1484 if isinstance(element, str):
1485 # If the key is not in the list element sting,
1486 # then continue for the next element in the list.
1487 if str(key) not in str(element):
1488 continue
1489 if isinstance(value, str):
1490 plugin_args[index] = element.replace(
1491 key, value
1492 )
1493 else:
1494 plugin_args[index] = global_plugin_dict[
1495 element
1496 ]
1497 # break
1498 except KeyError as e:
1499 print(f"Exception {e}")
1500 pass
George Keishing450f92f2025-05-15 23:12:51 +05301501
1502 """
1503 Example of plugin_func:
1504 plugin.redfish.enumerate_request(
1505 "xx.xx.xx.xx:443",
1506 "root",
1507 "********",
1508 "/redfish/v1/",
1509 "json")
1510 """
George Keishing7bc5ce32025-05-19 19:15:20 +05301511 # For logging purpose to mask password.
1512 # List should be string element to join else gives TypeError
1513 args_string = self.print_plugin_args_string(plugin_args)
George Keishingb97a9042021-07-29 07:41:20 -05001514
George Keishing7bc5ce32025-05-19 19:15:20 +05301515 # If user wants to debug plugins.
1516 self.logger.debug(
1517 f"\tDebug Plugin function: \n\t\t{plugin_name}."
1518 f"{plugin_function}{args_string}"
1519 )
1520
1521 # For generic logging plugin info.
1522 self.logger.info(
1523 f"\tPlugin function: \n\t\t{plugin_name}."
1524 f"{plugin_function}()"
1525 )
1526
1527 # Execute the plugins function with args.
1528 resp = execute_python_function(
1529 plugin_name, plugin_function, *plugin_args
1530 )
1531 self.logger.info(f"\tPlugin response = {resp}")
1532 # Update plugin vars dict if there is any.
1533 if resp != "PLUGIN_EXEC_ERROR":
1534 self.process_response_args_data(resp)
George Keishingb97a9042021-07-29 07:41:20 -05001535 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001536 # Set the plugin error state.
George Keishing7bc5ce32025-05-19 19:15:20 +05301537 global_plugin_error_dict["exit_on_error"] = True
George Keishing1e7b0182021-08-06 14:05:54 -05001538 self.logger.error("\tERROR: execute_plugin_block: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001539 pass
1540
George Keishing73b95d12021-08-13 14:30:52 -05001541 # There is a real error executing the plugin function.
George Keishing7bc5ce32025-05-19 19:15:20 +05301542 if resp == "PLUGIN_EXEC_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001543 return resp
1544
George Keishingde79a9b2021-08-12 16:14:43 -05001545 # Check if plugin_expects_return (int, string, list,dict etc)
Patrick Williams20f38712022-12-08 06:18:26 -06001546 if any("plugin_expects_return" in d for d in plugin_cmd_list):
1547 idx = self.key_index_list_dict(
1548 "plugin_expects_return", plugin_cmd_list
1549 )
1550 plugin_expects = plugin_cmd_list[idx]["plugin_expects_return"]
George Keishingde79a9b2021-08-12 16:14:43 -05001551 if plugin_expects:
1552 if resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001553 if (
1554 self.plugin_expect_type(plugin_expects, resp)
1555 == "INVALID"
1556 ):
George Keishingde79a9b2021-08-12 16:14:43 -05001557 self.logger.error("\tWARN: Plugin error check skipped")
1558 elif not self.plugin_expect_type(plugin_expects, resp):
Patrick Williams20f38712022-12-08 06:18:26 -06001559 self.logger.error(
1560 "\tERROR: Plugin expects return data: %s"
1561 % plugin_expects
1562 )
George Keishing7bc5ce32025-05-19 19:15:20 +05301563 global_plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001564 elif not resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001565 self.logger.error(
1566 "\tERROR: Plugin func failed to return data"
1567 )
George Keishing7bc5ce32025-05-19 19:15:20 +05301568 global_plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001569
1570 return resp
1571
George Keishing7bc5ce32025-05-19 19:15:20 +05301572 def print_plugin_args_string(self, plugin_args):
George Keishingb97a9042021-07-29 07:41:20 -05001573 r"""
George Keishing7bc5ce32025-05-19 19:15:20 +05301574 Generate a string representation of plugin arguments, replacing the
1575 password if necessary.
George Keishingb97a9042021-07-29 07:41:20 -05001576
George Keishing7bc5ce32025-05-19 19:15:20 +05301577 This method generates a string representation of the provided plugin
1578 arguments, joining them with commas. If the password is present in the
1579 arguments, it is replaced with "********".
1580 The method returns the generated string. If an exception occurs during
1581 the process, the method logs a debug log and returns "(None)".
1582
1583 Parameters:
1584 plugin_args (list): A list of plugin arguments.
1585
1586 Returns:
1587 str: The generated string representation of the plugin arguments.
1588 """
1589 try:
1590 plugin_args_str = "(" + ", ".join(map(str, plugin_args)) + ")"
1591 if self.password in plugin_args_str:
1592 args_string = plugin_args_str.replace(
1593 self.password, "********"
1594 )
1595 else:
1596 args_string = plugin_args_str
1597 except Exception as e:
1598 self.logger.debug("\tWARN:Print args string : %s" % e)
1599 return "(None)"
1600
1601 return args_string
1602
1603 def process_response_args_data(self, plugin_resp):
1604 r"""
1605 Parse the plugin function response and update plugin return variables.
1606
1607 This method parses the response data from a plugin function and
1608 updates the plugin return variables accordingly. The method takes the
1609 plugin_resp argument, which is expected to be the response data from a
1610 plugin function.
1611
1612 The method handles various data types (string, bytes,
1613 tuple, list, int, float) and updates the global global_plugin_dict
1614 dictionary with the parsed response data. If there is an error during
1615 the process, the method logs a warning and continues with the next
1616 plugin block execution.
1617
1618 Parameters:
1619 plugin_resp (Any): The response data from the plugin function.
1620
1621 Returns:
1622 None
George Keishingb97a9042021-07-29 07:41:20 -05001623 """
1624 resp_list = []
George Keishing5765f792021-08-02 13:08:53 -05001625 resp_data = ""
George Keishing9348b402021-08-13 12:22:35 -05001626
George Keishingb97a9042021-07-29 07:41:20 -05001627 # There is nothing to update the plugin response.
Patrick Williams20f38712022-12-08 06:18:26 -06001628 if len(global_plugin_list) == 0 or plugin_resp == "None":
George Keishingb97a9042021-07-29 07:41:20 -05001629 return
1630
George Keishing5765f792021-08-02 13:08:53 -05001631 if isinstance(plugin_resp, str):
Patrick Williams20f38712022-12-08 06:18:26 -06001632 resp_data = plugin_resp.strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001633 resp_list.append(resp_data)
1634 elif isinstance(plugin_resp, bytes):
Patrick Williams20f38712022-12-08 06:18:26 -06001635 resp_data = str(plugin_resp, "UTF-8").strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001636 resp_list.append(resp_data)
1637 elif isinstance(plugin_resp, tuple):
1638 if len(global_plugin_list) == 1:
George Keishing7bc5ce32025-05-19 19:15:20 +05301639 resp_list.append(list(plugin_resp))
George Keishing5765f792021-08-02 13:08:53 -05001640 else:
1641 resp_list = list(plugin_resp)
George Keishing7bc5ce32025-05-19 19:15:20 +05301642 resp_list = [x for x in resp_list]
George Keishingb97a9042021-07-29 07:41:20 -05001643 elif isinstance(plugin_resp, list):
George Keishing5765f792021-08-02 13:08:53 -05001644 if len(global_plugin_list) == 1:
Patrick Williams20f38712022-12-08 06:18:26 -06001645 resp_list.append([x.strip("\r\n\t") for x in plugin_resp])
George Keishing5765f792021-08-02 13:08:53 -05001646 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001647 resp_list = [x.strip("\r\n\t") for x in plugin_resp]
George Keishing5765f792021-08-02 13:08:53 -05001648 elif isinstance(plugin_resp, int) or isinstance(plugin_resp, float):
1649 resp_list.append(plugin_resp)
George Keishingb97a9042021-07-29 07:41:20 -05001650
George Keishing9348b402021-08-13 12:22:35 -05001651 # Iterate if there is a list of plugin return vars to update.
George Keishingb97a9042021-07-29 07:41:20 -05001652 for idx, item in enumerate(resp_list, start=0):
George Keishing9348b402021-08-13 12:22:35 -05001653 # Exit loop, done required loop.
George Keishingb97a9042021-07-29 07:41:20 -05001654 if idx >= len(global_plugin_list):
1655 break
1656 # Find the index of the return func in the list and
1657 # update the global func return dictionary.
1658 try:
1659 dict_idx = global_plugin_list[idx]
1660 global_plugin_dict[dict_idx] = item
1661 except (IndexError, ValueError) as e:
George Keishing7bc5ce32025-05-19 19:15:20 +05301662 self.logger.warn("\tWARN: process_response_args_data: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001663 pass
1664
1665 # Done updating plugin dict irrespective of pass or failed,
George Keishing9348b402021-08-13 12:22:35 -05001666 # clear all the list element for next plugin block execute.
George Keishingb97a9042021-07-29 07:41:20 -05001667 global_plugin_list.clear()
1668
George Keishingb97a9042021-07-29 07:41:20 -05001669 def yaml_args_populate(self, yaml_arg_list):
1670 r"""
George Keishingf0eb1d62025-05-14 15:07:02 +05301671 Decode environment and plugin variables and populate the argument list.
George Keishingb97a9042021-07-29 07:41:20 -05001672
George Keishingf0eb1d62025-05-14 15:07:02 +05301673 This method processes the yaml_arg_list argument, which is expected to
1674 contain a list of arguments read from a YAML file. The method iterates
1675 through the list, decodes environment and plugin variables, and
1676 returns a populated list of arguments.
George Keishingb97a9042021-07-29 07:41:20 -05001677
George Keishingf0eb1d62025-05-14 15:07:02 +05301678 .. code-block:: yaml
1679
George Keishingb97a9042021-07-29 07:41:20 -05001680 - plugin_args:
1681 - arg1
1682 - arg2
1683
George Keishingf0eb1d62025-05-14 15:07:02 +05301684 ['${hostname}:${port_https}', '${username}', '/redfish/v1/', 'json']
George Keishingb97a9042021-07-29 07:41:20 -05001685
George Keishingf0eb1d62025-05-14 15:07:02 +05301686 Returns the populated plugin list
1687 ['xx.xx.xx.xx:443', 'root', '/redfish/v1/', 'json']
1688
1689 Parameters:
1690 yaml_arg_list (list): A list of arguments containing environment
1691 and plugin variables.
1692
1693 Returns:
1694 list: A populated list of arguments with decoded environment and
1695 plugin variables.
1696 """
George Keishingb97a9042021-07-29 07:41:20 -05001697 if isinstance(yaml_arg_list, list):
George Keishingf0eb1d62025-05-14 15:07:02 +05301698 populated_list = []
George Keishingb97a9042021-07-29 07:41:20 -05001699 for arg in yaml_arg_list:
George Keishing0581cb02021-08-05 15:08:58 -05001700 if isinstance(arg, (int, float)):
George Keishingf0eb1d62025-05-14 15:07:02 +05301701 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001702 elif isinstance(arg, str):
George Keishing7bc5ce32025-05-19 19:15:20 +05301703 arg_str = self.yaml_env_and_plugin_vars_populate(arg)
George Keishingf0eb1d62025-05-14 15:07:02 +05301704 populated_list.append(arg_str)
George Keishingb97a9042021-07-29 07:41:20 -05001705 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301706 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001707
George Keishingf0eb1d62025-05-14 15:07:02 +05301708 return populated_list
George Keishingb97a9042021-07-29 07:41:20 -05001709
1710 def yaml_env_and_plugin_vars_populate(self, yaml_arg_str):
1711 r"""
George Keishinga593f4b2025-05-13 20:02:36 +05301712 Update environment variables and plugin variables based on the
1713 provided YAML argument string.
George Keishingb97a9042021-07-29 07:41:20 -05001714
George Keishinga593f4b2025-05-13 20:02:36 +05301715 This method processes the yaml_arg_str argument, which is expected
1716 to contain a string representing environment variables and plugin
1717 variables in the format:
George Keishingb97a9042021-07-29 07:41:20 -05001718
George Keishinga593f4b2025-05-13 20:02:36 +05301719 .. code-block:: yaml
1720
George Keishingb97a9042021-07-29 07:41:20 -05001721 - cat ${MY_VAR}
1722 - ls -AX my_plugin_var
George Keishinga593f4b2025-05-13 20:02:36 +05301723
1724 The method parses the string, extracts the variable names, and updates
1725 the corresponding environment variables and plugin variables.
1726
1727 Parameters:
1728 yaml_arg_str (str): A string containing environment and plugin
1729 variable definitions in YAML format.
1730
1731 Returns:
1732 str: The updated YAML argument string with plugin variables
1733 replaced.
George Keishingb97a9042021-07-29 07:41:20 -05001734 """
George Keishinga593f4b2025-05-13 20:02:36 +05301735
1736 # Parse and convert the Plugin YAML vars string to python vars
1737 # Example:
1738 # ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
George Keishingb97a9042021-07-29 07:41:20 -05001739 try:
George Keishingc754b432025-04-24 14:27:14 +05301740 # Example, list of matching
1741 # env vars ['username', 'password', 'hostname']
George Keishingb97a9042021-07-29 07:41:20 -05001742 # Extra escape \ for special symbols. '\$\{([^\}]+)\}' works good.
George Keishinga593f4b2025-05-13 20:02:36 +05301743 env_var_regex = r"\$\{([^\}]+)\}"
1744 env_var_names_list = re.findall(env_var_regex, yaml_arg_str)
1745
George Keishing7bc5ce32025-05-19 19:15:20 +05301746 # If the list in empty [] nothing to update.
1747 if not len(env_var_names_list):
1748 return yaml_arg_str
George Keishingb97a9042021-07-29 07:41:20 -05001749 for var in env_var_names_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301750 env_var = os.environ.get(var)
1751 if env_var:
1752 env_replace = "${" + var + "}"
1753 yaml_arg_str = yaml_arg_str.replace(env_replace, env_var)
George Keishingb97a9042021-07-29 07:41:20 -05001754 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001755 self.logger.error("\tERROR:yaml_env_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001756 pass
1757
George Keishinga593f4b2025-05-13 20:02:36 +05301758 """
1759 Parse the string for plugin vars.
1760 Implement the logic to update environment variables based on the
1761 extracted variable names.
1762 """
George Keishingb97a9042021-07-29 07:41:20 -05001763 try:
George Keishinga593f4b2025-05-13 20:02:36 +05301764 # Example, list of plugin vars env_var_names_list
1765 # ['my_hostname', 'port_https']
1766 global_plugin_dict_keys = set(global_plugin_dict.keys())
1767 # Skip env var list already populated above code block list.
1768 plugin_var_name_list = [
1769 var
1770 for var in global_plugin_dict_keys
1771 if var not in env_var_names_list
1772 ]
1773
George Keishingb97a9042021-07-29 07:41:20 -05001774 for var in plugin_var_name_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301775 plugin_var_value = global_plugin_dict[var]
George Keishing0581cb02021-08-05 15:08:58 -05001776 if yaml_arg_str in global_plugin_dict:
George Keishinga593f4b2025-05-13 20:02:36 +05301777 """
1778 If this plugin var exist but empty in dict, don't replace.
1779 his is either a YAML plugin statement incorrectly used or
1780 user added a plugin var which is not going to be populated.
1781 """
1782 if isinstance(plugin_var_value, (list, dict)):
1783 """
1784 List data type or dict can't be replaced, use
George Keishing7bc5ce32025-05-19 19:15:20 +05301785 directly in plugin function call.
George Keishinga593f4b2025-05-13 20:02:36 +05301786 """
George Keishing0581cb02021-08-05 15:08:58 -05001787 global_plugin_type_list.append(var)
1788 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001789 yaml_arg_str = yaml_arg_str.replace(
George Keishinga593f4b2025-05-13 20:02:36 +05301790 str(var), str(plugin_var_value)
Patrick Williams20f38712022-12-08 06:18:26 -06001791 )
George Keishingb97a9042021-07-29 07:41:20 -05001792 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001793 self.logger.error("\tERROR: yaml_plugin_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001794 pass
1795
George Keishinga593f4b2025-05-13 20:02:36 +05301796 # From ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
1797 # to populated values string as
1798 # Example: xx.xx.xx.xx:443 and return the string
George Keishingb97a9042021-07-29 07:41:20 -05001799 return yaml_arg_str
George Keishing1e7b0182021-08-06 14:05:54 -05001800
1801 def plugin_error_check(self, plugin_dict):
1802 r"""
George Keishing1e877422025-05-09 20:45:09 +05301803 Process plugin error dictionary and return the corresponding error
1804 message.
George Keishing1e7b0182021-08-06 14:05:54 -05001805
George Keishing1e877422025-05-09 20:45:09 +05301806 This method checks if any dictionary in the plugin_dict list contains
1807 a "plugin_error" key. If such a dictionary is found, it retrieves the
1808 value associated with the "plugin_error" key and returns the
George Keishing7bc5ce32025-05-19 19:15:20 +05301809 corresponding error message from the global_plugin_error_dict
1810 attribute.
George Keishing1e877422025-05-09 20:45:09 +05301811
1812 Parameters:
1813 plugin_dict (list of dict): A list of dictionaries containing
1814 plugin error information.
1815
1816 Returns:
1817 str: The error message corresponding to the "plugin_error" value,
1818 or None if no error is found.
George Keishing1e7b0182021-08-06 14:05:54 -05001819 """
Patrick Williams20f38712022-12-08 06:18:26 -06001820 if any("plugin_error" in d for d in plugin_dict):
George Keishing1e7b0182021-08-06 14:05:54 -05001821 for d in plugin_dict:
Patrick Williams20f38712022-12-08 06:18:26 -06001822 if "plugin_error" in d:
1823 value = d["plugin_error"]
George Keishing7bc5ce32025-05-19 19:15:20 +05301824 return global_plugin_error_dict.get(value, None)
George Keishing1e877422025-05-09 20:45:09 +05301825 return None
George Keishingde79a9b2021-08-12 16:14:43 -05001826
1827 def key_index_list_dict(self, key, list_dict):
1828 r"""
George Keishing1e877422025-05-09 20:45:09 +05301829 Find the index of the first dictionary in the list that contains
1830 the specified key.
George Keishingde79a9b2021-08-12 16:14:43 -05001831
George Keishing1e877422025-05-09 20:45:09 +05301832 Parameters:
1833 key (str): The key to search for in the
1834 dictionaries.
1835 list_dict (list of dict): A list of dictionaries to search
1836 through.
1837
1838 Returns:
1839 int: The index of the first dictionary containing the key, or -1
1840 if no match is found.
George Keishingde79a9b2021-08-12 16:14:43 -05001841 """
1842 for i, d in enumerate(list_dict):
George Keishing1e877422025-05-09 20:45:09 +05301843 if key in d:
George Keishingde79a9b2021-08-12 16:14:43 -05001844 return i
George Keishing1e877422025-05-09 20:45:09 +05301845 return -1
George Keishingde79a9b2021-08-12 16:14:43 -05001846
1847 def plugin_expect_type(self, type, data):
1848 r"""
George Keishing1e877422025-05-09 20:45:09 +05301849 Check if the provided data matches the expected type.
1850
1851 This method checks if the data argument matches the specified type.
1852 It supports the following types: "int", "float", "str", "list", "dict",
1853 and "tuple".
1854
1855 If the type is not recognized, it logs an info message and returns
1856 "INVALID".
1857
1858 Parameters:
1859 type (str): The expected data type.
1860 data: The data to check against the expected type.
1861
1862 Returns:
1863 bool or str: True if the data matches the expected type, False if
1864 not, or "INVALID" if the type is not recognized.
George Keishingde79a9b2021-08-12 16:14:43 -05001865 """
Patrick Williams20f38712022-12-08 06:18:26 -06001866 if type == "int":
George Keishingde79a9b2021-08-12 16:14:43 -05001867 return isinstance(data, int)
Patrick Williams20f38712022-12-08 06:18:26 -06001868 elif type == "float":
George Keishingde79a9b2021-08-12 16:14:43 -05001869 return isinstance(data, float)
Patrick Williams20f38712022-12-08 06:18:26 -06001870 elif type == "str":
George Keishingde79a9b2021-08-12 16:14:43 -05001871 return isinstance(data, str)
Patrick Williams20f38712022-12-08 06:18:26 -06001872 elif type == "list":
George Keishingde79a9b2021-08-12 16:14:43 -05001873 return isinstance(data, list)
Patrick Williams20f38712022-12-08 06:18:26 -06001874 elif type == "dict":
George Keishingde79a9b2021-08-12 16:14:43 -05001875 return isinstance(data, dict)
Patrick Williams20f38712022-12-08 06:18:26 -06001876 elif type == "tuple":
George Keishingde79a9b2021-08-12 16:14:43 -05001877 return isinstance(data, tuple)
1878 else:
1879 self.logger.info("\tInvalid data type requested: %s" % type)
Patrick Williams20f38712022-12-08 06:18:26 -06001880 return "INVALID"