blob: 0f89581e67ad73f2ea2b34324771b9de19ec48a5 [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"]
George Keishing473643e2025-05-22 23:54:06 +0530812 # Update any global reserved variable name with value in dict.
813 list_of_commands = self.update_vars_with_env_values(
814 global_plugin_dict, list_of_commands
815 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500816 except KeyError:
817 list_of_commands = []
818 return list_of_commands
819
Patrick Williams20f38712022-12-08 06:18:26 -0600820 def get_file_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500821 r"""
George Keishing04416092025-05-17 18:47:10 +0530822 Fetch a list of files from the configuration file.
Peter D Phanbabf2962021-07-07 11:24:40 -0500823
George Keishing04416092025-05-17 18:47:10 +0530824 This method retrieves a list of files from the
825 ffdc_actions_for_target_type dictionary, which contains commands and
826 files for the selected remote host type. The method returns the list
827 of files.
828
829 Parameters:
830 ffdc_actions_for_target_type (dict): A dictionary containing
831 commands and files for the
832 selected remote host type.
833
834 Returns:
835 list: A list of files.
Peter D Phanbabf2962021-07-07 11:24:40 -0500836 """
837 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600838 list_of_files = ffdc_actions_for_target_type["FILES"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500839 except KeyError:
840 list_of_files = []
841 return list_of_files
842
Patrick Williams20f38712022-12-08 06:18:26 -0600843 def unpack_command(self, command):
Peter D Phan5963d632021-07-12 09:58:55 -0500844 r"""
George Keishing04416092025-05-17 18:47:10 +0530845 Unpack a command from the configuration file, handling both dictionary
846 and string inputs.
Peter D Phan5963d632021-07-12 09:58:55 -0500847
George Keishing04416092025-05-17 18:47:10 +0530848 This method takes a command from the configuration file, which can be
849 either a dictionary or a string. If the input is a dictionary, the
850 method extracts the command text and timeout from the dictionary.
851 If the input is a string, the method assumes a default timeout of
852 60 seconds.
853 The method returns a tuple containing the command text and timeout.
854
855 Parameters:
856 command (dict or str): A command from the configuration file,
857 which can be either a dictionary or a
858 string.
859
860 Returns:
861 tuple: A tuple containing the command text and timeout.
Peter D Phan5963d632021-07-12 09:58:55 -0500862 """
863 if isinstance(command, dict):
864 command_txt = next(iter(command))
865 command_timeout = next(iter(command.values()))
866 elif isinstance(command, str):
867 command_txt = command
868 # Default command timeout 60 seconds
869 command_timeout = 60
870
871 return command_txt, command_timeout
872
Patrick Williams20f38712022-12-08 06:18:26 -0600873 def ssh_execute_ffdc_commands(
874 self, ffdc_actions_for_target_type, form_filename=False
875 ):
Peter D Phan3beb02e2021-07-06 13:25:17 -0500876 r"""
George Keishing04416092025-05-17 18:47:10 +0530877 Send commands in the ffdc_config file to the targeted system using SSH.
Peter D Phan3beb02e2021-07-06 13:25:17 -0500878
George Keishing04416092025-05-17 18:47:10 +0530879 This method sends a set of commands and collects FFDC data from the
880 targeted system using the SSH protocol. The method takes the
881 ffdc_actions_for_target_type dictionary and an optional
882 form_filename parameter as arguments.
883
884 If form_filename is set to True, the method prepends the target type
885 to the output file name. The method returns the output of the executed
886 commands.
887
888 It also prints the progress counter string + on the console.
889
890 Parameters:
891 ffdc_actions_for_target_type (dict): A dictionary containing
892 commands and files for the
893 selected remote host type.
894 form_filename (bool, optional): If True, prepends the target
895 type to the output file name.
896 Defaults to False.
897
898 Returns:
899 None
Peter D Phan3beb02e2021-07-06 13:25:17 -0500900 """
Patrick Williams20f38712022-12-08 06:18:26 -0600901 self.logger.info(
902 "\n\t[Run] Executing commands on %s using %s"
903 % (self.hostname, ffdc_actions_for_target_type["PROTOCOL"][0])
904 )
Peter D Phan3beb02e2021-07-06 13:25:17 -0500905
George Keishingf5a57502021-07-22 16:43:47 -0500906 list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500907 # If command list is empty, returns
908 if not list_of_commands:
909 return
910
911 progress_counter = 0
912 for command in list_of_commands:
Peter D Phan5963d632021-07-12 09:58:55 -0500913 command_txt, command_timeout = self.unpack_command(command)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500914
915 if form_filename:
916 command_txt = str(command_txt % self.target_type)
917
Patrick Williams20f38712022-12-08 06:18:26 -0600918 (
919 cmd_exit_code,
920 err,
921 response,
922 ) = self.ssh_remoteclient.execute_command(
923 command_txt, command_timeout
924 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500925
926 if cmd_exit_code:
927 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600928 "\n\t\t[WARN] %s exits with code %s."
929 % (command_txt, str(cmd_exit_code))
930 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500931 self.logger.warning("\t\t[WARN] %s " % err)
Peter D Phanbabf2962021-07-07 11:24:40 -0500932
Peter D Phan3beb02e2021-07-06 13:25:17 -0500933 progress_counter += 1
934 self.print_progress(progress_counter)
935
Peter D Phane86d9a52021-07-15 10:42:25 -0500936 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan3beb02e2021-07-06 13:25:17 -0500937
Patrick Williams20f38712022-12-08 06:18:26 -0600938 def group_copy(self, ffdc_actions_for_target_type):
Peter D Phan56429a62021-06-23 08:38:29 -0500939 r"""
George Keishing04416092025-05-17 18:47:10 +0530940 SCP a group of files (wildcard) from the remote host.
Peter D Phan56429a62021-06-23 08:38:29 -0500941
George Keishing04416092025-05-17 18:47:10 +0530942 This method copies a group of files from the remote host using the SCP
943 protocol. The method takes the fdc_actions_for_target_type dictionary
944 as an argument, which contains commands and files for the selected
945 remote host type.
946
947 Parameters:
948 fdc_actions_for_target_type (dict): A dictionary containing
949 commands and files for the
950 selected remote host type.
951
952 Returns:
953 None
Peter D Phan56429a62021-06-23 08:38:29 -0500954 """
Peter D Phan5963d632021-07-12 09:58:55 -0500955 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600956 self.logger.info(
957 "\n\tCopying files from remote system %s via SCP.\n"
958 % self.hostname
959 )
Peter D Phan56429a62021-06-23 08:38:29 -0500960
Patrick Williams20f38712022-12-08 06:18:26 -0600961 list_of_commands = self.get_command_list(
962 ffdc_actions_for_target_type
963 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500964 # If command list is empty, returns
965 if not list_of_commands:
966 return
Peter D Phan56429a62021-06-23 08:38:29 -0500967
Peter D Phanbabf2962021-07-07 11:24:40 -0500968 for command in list_of_commands:
969 try:
George Keishingb4540e72021-08-02 13:48:46 -0500970 command = self.yaml_env_and_plugin_vars_populate(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500971 except IndexError:
George Keishingb4540e72021-08-02 13:48:46 -0500972 self.logger.error("\t\tInvalid command %s" % command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500973 continue
974
Patrick Williams20f38712022-12-08 06:18:26 -0600975 (
976 cmd_exit_code,
977 err,
978 response,
979 ) = self.ssh_remoteclient.execute_command(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500980
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500981 # If file does not exist, code take no action.
982 # cmd_exit_code is ignored for this scenario.
Peter D Phan56429a62021-06-23 08:38:29 -0500983 if response:
Patrick Williams20f38712022-12-08 06:18:26 -0600984 scp_result = self.ssh_remoteclient.scp_file_from_remote(
985 response.split("\n"), self.ffdc_dir_path
986 )
Peter D Phan56429a62021-06-23 08:38:29 -0500987 if scp_result:
Patrick Williams20f38712022-12-08 06:18:26 -0600988 self.logger.info(
989 "\t\tSuccessfully copied from "
990 + self.hostname
991 + ":"
992 + command
993 )
Peter D Phan56429a62021-06-23 08:38:29 -0500994 else:
George Keishinga56e87b2021-08-06 00:24:19 -0500995 self.logger.info("\t\t%s has no result" % command)
Peter D Phan56429a62021-06-23 08:38:29 -0500996
997 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600998 self.logger.info(
999 "\n\n\tSkip copying files from remote system %s.\n"
1000 % self.hostname
1001 )
Peter D Phan56429a62021-06-23 08:38:29 -05001002
Patrick Williams20f38712022-12-08 06:18:26 -06001003 def scp_ffdc(
1004 self,
1005 targ_dir_path,
1006 targ_file_prefix,
1007 form_filename,
1008 file_list=None,
1009 quiet=None,
1010 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -05001011 r"""
George Keishing04416092025-05-17 18:47:10 +05301012 SCP all files in the file_dict to the indicated directory on the local
George Keishingc754b432025-04-24 14:27:14 +05301013 system.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001014
George Keishing04416092025-05-17 18:47:10 +05301015 This method copies all files specified in the file_dict dictionary
1016 from the targeted system to the local system using the SCP protocol.
1017 The method takes the target directory path, target file prefix, and a
1018 boolean flag form_filename as required arguments.
1019
1020 The file_dict argument is optional and contains the files to be copied.
1021 The quiet argument is also optional and, if set to True, suppresses
1022 the output of the SCP operation.
1023
1024 Parameters:
1025 targ_dir_path (str): The path of the directory to receive
1026 the files on the local system.
1027 targ_file_prefix (str): Prefix which will be prepended to each
Peter D Phan72ce6b82021-06-03 06:18:26 -05001028 target file's name.
George Keishing04416092025-05-17 18:47:10 +05301029 form_filename (bool): If True, prepends the target type to
1030 the file names.
1031 file_dict (dict, optional): A dictionary containing the files to
1032 be copied. Defaults to None.
1033 quiet (bool, optional): If True, suppresses the output of the
1034 SCP operation. Defaults to None.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001035
George Keishing04416092025-05-17 18:47:10 +05301036 Returns:
1037 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001038 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001039 progress_counter = 0
1040 for filename in file_list:
Peter D Phan2b8052d2021-06-22 10:55:41 -05001041 if form_filename:
1042 filename = str(filename % self.target_type)
Peter D Phan72ce6b82021-06-03 06:18:26 -05001043 source_file_path = filename
Patrick Williams20f38712022-12-08 06:18:26 -06001044 targ_file_path = (
1045 targ_dir_path + targ_file_prefix + filename.split("/")[-1]
1046 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001047
Peter D Phanbabf2962021-07-07 11:24:40 -05001048 # If source file name contains wild card, copy filename as is.
Patrick Williams20f38712022-12-08 06:18:26 -06001049 if "*" in source_file_path:
1050 scp_result = self.ssh_remoteclient.scp_file_from_remote(
1051 source_file_path, self.ffdc_dir_path
1052 )
Peter D Phanbabf2962021-07-07 11:24:40 -05001053 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001054 scp_result = self.ssh_remoteclient.scp_file_from_remote(
1055 source_file_path, targ_file_path
1056 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001057
1058 if not quiet:
1059 if scp_result:
Peter D Phane86d9a52021-07-15 10:42:25 -05001060 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -06001061 "\t\tSuccessfully copied from "
1062 + self.hostname
1063 + ":"
1064 + source_file_path
1065 + ".\n"
1066 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001067 else:
Peter D Phane86d9a52021-07-15 10:42:25 -05001068 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -06001069 "\t\tFail to copy from "
1070 + self.hostname
1071 + ":"
1072 + source_file_path
1073 + ".\n"
1074 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001075 else:
1076 progress_counter += 1
1077 self.print_progress(progress_counter)
1078
Peter D Phan5e56f522021-12-20 13:19:41 -06001079 def set_ffdc_default_store_path(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -05001080 r"""
George Keishing04416092025-05-17 18:47:10 +05301081 Set default values for self.ffdc_dir_path and self.ffdc_prefix.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001082
George Keishing04416092025-05-17 18:47:10 +05301083 This method sets default values for the self.ffdc_dir_path and
1084 self.ffdc_prefix class variables.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001085
George Keishing04416092025-05-17 18:47:10 +05301086 The collected FFDC files will be stored in the directory
1087 /self.location/hostname_timestr/, with individual files having the
1088 format timestr_filename where timestr is in %Y%m%d-%H%M%S.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001089
George Keishing04416092025-05-17 18:47:10 +05301090 Returns:
1091 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001092 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001093 timestr = time.strftime("%Y%m%d-%H%M%S")
Patrick Williams20f38712022-12-08 06:18:26 -06001094 self.ffdc_dir_path = (
1095 self.location + "/" + self.hostname + "_" + timestr + "/"
1096 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001097 self.ffdc_prefix = timestr + "_"
1098 self.validate_local_store(self.ffdc_dir_path)
1099
Peter D Phan5e56f522021-12-20 13:19:41 -06001100 # Need to verify local store path exists prior to instantiate this class.
George Keishing04416092025-05-17 18:47:10 +05301101 # This class method to validate log path before referencing this class.
Peter D Phan5e56f522021-12-20 13:19:41 -06001102 @classmethod
1103 def validate_local_store(cls, dir_path):
Peter D Phan72ce6b82021-06-03 06:18:26 -05001104 r"""
George Keishing04416092025-05-17 18:47:10 +05301105 Ensure the specified directory exists to store FFDC files locally.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001106
George Keishing04416092025-05-17 18:47:10 +05301107 This method checks if the provided dir_path exists. If the directory
1108 does not exist, the method creates it. The method does not return any
1109 value.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001110
George Keishing04416092025-05-17 18:47:10 +05301111 Parameters:
1112 dir_path (str): The directory path where collected FFDC data files
1113 will be stored.
1114
1115 Returns:
1116 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001117 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001118 if not os.path.exists(dir_path):
1119 try:
George Keishing7b3a5132021-07-13 09:24:02 -05001120 os.makedirs(dir_path, 0o755)
Peter D Phan72ce6b82021-06-03 06:18:26 -05001121 except (IOError, OSError) as e:
1122 # PermissionError
1123 if e.errno == EPERM or e.errno == EACCES:
George Keishing15352052025-04-24 18:55:47 +05301124 print(
Patrick Williams20f38712022-12-08 06:18:26 -06001125 "\tERROR: os.makedirs %s failed with"
1126 " PermissionError.\n" % dir_path
1127 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001128 else:
George Keishing15352052025-04-24 18:55:47 +05301129 print(
Patrick Williams20f38712022-12-08 06:18:26 -06001130 "\tERROR: os.makedirs %s failed with %s.\n"
1131 % (dir_path, e.strerror)
1132 )
Peter D Phan72ce6b82021-06-03 06:18:26 -05001133 sys.exit(-1)
1134
1135 def print_progress(self, progress):
1136 r"""
George Keishing04416092025-05-17 18:47:10 +05301137 Print the current activity progress.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001138
George Keishing04416092025-05-17 18:47:10 +05301139 This method prints the current activity progress using the provided
1140 progress counter. The method does not return any value.
Peter D Phan72ce6b82021-06-03 06:18:26 -05001141
George Keishing04416092025-05-17 18:47:10 +05301142 Parameters:
1143 progress (int): The current activity progress counter.
1144
1145 Returns:
1146 None
Peter D Phan72ce6b82021-06-03 06:18:26 -05001147 """
Peter D Phan72ce6b82021-06-03 06:18:26 -05001148 sys.stdout.write("\r\t" + "+" * progress)
1149 sys.stdout.flush()
Patrick Williams20f38712022-12-08 06:18:26 -06001150 time.sleep(0.1)
Peter D Phan0c669772021-06-24 13:52:42 -05001151
1152 def verify_redfish(self):
1153 r"""
George Keishing04416092025-05-17 18:47:10 +05301154 Verify if the remote host has the Redfish service active.
Peter D Phan0c669772021-06-24 13:52:42 -05001155
George Keishing04416092025-05-17 18:47:10 +05301156 This method checks if the remote host has the Redfish service active
1157 by sending a GET request to the Redfish base URL /redfish/v1/.
1158 If the request is successful (status code 200), the method returns
1159 stdout output of the run else error message.
1160
1161 Returns:
1162 str: Redfish service executed output.
Peter D Phan0c669772021-06-24 13:52:42 -05001163 """
Patrick Williams20f38712022-12-08 06:18:26 -06001164 redfish_parm = (
1165 "redfishtool -r "
1166 + self.hostname
George Keishing7a61aa22023-06-26 13:18:37 +05301167 + ":"
1168 + self.port_https
Patrick Williams20f38712022-12-08 06:18:26 -06001169 + " -S Always raw GET /redfish/v1/"
1170 )
1171 return self.run_tool_cmd(redfish_parm, True)
Peter D Phan0c669772021-06-24 13:52:42 -05001172
George Keishingeafba182021-06-29 13:44:58 -05001173 def verify_ipmi(self):
1174 r"""
George Keishing04416092025-05-17 18:47:10 +05301175 Verify if the remote host has the IPMI LAN service active.
George Keishingeafba182021-06-29 13:44:58 -05001176
George Keishing04416092025-05-17 18:47:10 +05301177 This method checks if the remote host has the IPMI LAN service active
1178 by sending an IPMI "power status" command.
1179
1180 If the command is successful (returns a non-empty response),
1181 else error message.
1182
1183 Returns:
1184 str: IPMI LAN service executed output.
George Keishingeafba182021-06-29 13:44:58 -05001185 """
Patrick Williams20f38712022-12-08 06:18:26 -06001186 if self.target_type == "OPENBMC":
1187 ipmi_parm = (
1188 "ipmitool -I lanplus -C 17 -U "
1189 + self.username
1190 + " -P "
1191 + self.password
1192 + " -H "
1193 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +05301194 + " -p "
1195 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -06001196 + " power status"
1197 )
George Keishing484f8242021-07-27 01:42:02 -05001198 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001199 ipmi_parm = (
1200 "ipmitool -I lanplus -P "
1201 + self.password
1202 + " -H "
1203 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +05301204 + " -p "
1205 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -06001206 + " power status"
1207 )
George Keishing484f8242021-07-27 01:42:02 -05001208
Patrick Williams20f38712022-12-08 06:18:26 -06001209 return self.run_tool_cmd(ipmi_parm, True)
George Keishingeafba182021-06-29 13:44:58 -05001210
Patrick Williams20f38712022-12-08 06:18:26 -06001211 def run_tool_cmd(self, parms_string, quiet=False):
George Keishingeafba182021-06-29 13:44:58 -05001212 r"""
George Keishing04416092025-05-17 18:47:10 +05301213 Run a CLI standard tool or script with the provided command options.
George Keishingeafba182021-06-29 13:44:58 -05001214
George Keishing04416092025-05-17 18:47:10 +05301215 This method runs a CLI standard tool or script with the provided
1216 parms_string command options. If the quiet parameter is set to True,
1217 the method suppresses the output of the command.
1218 The method returns the output of the command as a string.
1219
1220 Parameters:
1221 parms_string (str): The command options for the CLI tool or
1222 script.
1223 quiet (bool, optional): If True, suppresses the output of the
1224 command. Defaults to False.
1225
1226 Returns:
1227 str: The output of the command as a string.
George Keishingeafba182021-06-29 13:44:58 -05001228 """
1229
Patrick Williams20f38712022-12-08 06:18:26 -06001230 result = subprocess.run(
1231 [parms_string],
1232 stdout=subprocess.PIPE,
1233 stderr=subprocess.PIPE,
1234 shell=True,
1235 universal_newlines=True,
1236 )
George Keishingeafba182021-06-29 13:44:58 -05001237
1238 if result.stderr and not quiet:
George Keishing0e9b5ba2025-05-08 12:17:58 +05301239 if self.password in parms_string:
1240 parms_string = parms_string.replace(self.password, "********")
Patrick Williams20f38712022-12-08 06:18:26 -06001241 self.logger.error("\n\t\tERROR with %s " % parms_string)
1242 self.logger.error("\t\t" + result.stderr)
George Keishingeafba182021-06-29 13:44:58 -05001243
1244 return result.stdout
George Keishing04d29102021-07-16 02:05:57 -05001245
George Keishingf5a57502021-07-22 16:43:47 -05001246 def verify_protocol(self, protocol_list):
1247 r"""
George Keishing04416092025-05-17 18:47:10 +05301248 Perform a working check for the provided list of protocols.
George Keishingf5a57502021-07-22 16:43:47 -05001249
George Keishing04416092025-05-17 18:47:10 +05301250 This method checks if the specified protocols are available on the
1251 remote host. The method iterates through the protocol_list and
1252 attempts to establish a connection using each protocol.
1253
1254 If a connection is successfully established, the method append to the
1255 list and if any protocol fails to connect, the method ignores it.
1256
1257 Parameters:
1258 protocol_list (list): A list of protocols to check.
1259
1260 Returns:
1261 list: All protocols are available list.
George Keishingf5a57502021-07-22 16:43:47 -05001262 """
1263
1264 tmp_list = []
1265 if self.target_is_pingable():
1266 tmp_list.append("SHELL")
1267
1268 for protocol in protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -06001269 if self.remote_protocol != "ALL":
George Keishingf5a57502021-07-22 16:43:47 -05001270 if self.remote_protocol != protocol:
1271 continue
1272
1273 # Only check SSH/SCP once for both protocols
Patrick Williams20f38712022-12-08 06:18:26 -06001274 if (
1275 protocol == "SSH"
1276 or protocol == "SCP"
1277 and protocol not in tmp_list
1278 ):
George Keishingf5a57502021-07-22 16:43:47 -05001279 if self.ssh_to_target_system():
George Keishingaa638702021-07-26 11:48:28 -05001280 # Add only what user asked.
Patrick Williams20f38712022-12-08 06:18:26 -06001281 if self.remote_protocol != "ALL":
George Keishingaa638702021-07-26 11:48:28 -05001282 tmp_list.append(self.remote_protocol)
1283 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001284 tmp_list.append("SSH")
1285 tmp_list.append("SCP")
George Keishingf5a57502021-07-22 16:43:47 -05001286
Patrick Williams20f38712022-12-08 06:18:26 -06001287 if protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -05001288 if self.telnet_to_target_system():
1289 tmp_list.append(protocol)
1290
Patrick Williams20f38712022-12-08 06:18:26 -06001291 if protocol == "REDFISH":
George Keishingf5a57502021-07-22 16:43:47 -05001292 if self.verify_redfish():
1293 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001294 self.logger.info(
1295 "\n\t[Check] %s Redfish Service.\t\t [OK]"
1296 % self.hostname
1297 )
George Keishingf5a57502021-07-22 16:43:47 -05001298 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001299 self.logger.info(
1300 "\n\t[Check] %s Redfish Service.\t\t [NOT AVAILABLE]"
1301 % self.hostname
1302 )
George Keishingf5a57502021-07-22 16:43:47 -05001303
Patrick Williams20f38712022-12-08 06:18:26 -06001304 if protocol == "IPMI":
George Keishingf5a57502021-07-22 16:43:47 -05001305 if self.verify_ipmi():
1306 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001307 self.logger.info(
1308 "\n\t[Check] %s IPMI LAN Service.\t\t [OK]"
1309 % self.hostname
1310 )
George Keishingf5a57502021-07-22 16:43:47 -05001311 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001312 self.logger.info(
1313 "\n\t[Check] %s IPMI LAN Service.\t\t [NOT AVAILABLE]"
1314 % self.hostname
1315 )
George Keishingf5a57502021-07-22 16:43:47 -05001316
1317 return tmp_list
George Keishinge1686752021-07-27 12:55:28 -05001318
1319 def load_env(self):
1320 r"""
George Keishing0e9b5ba2025-05-08 12:17:58 +05301321 Load the user environment variables from a YAML file.
George Keishinge1686752021-07-27 12:55:28 -05001322
George Keishing0e9b5ba2025-05-08 12:17:58 +05301323 This method reads the environment variables from a YAML file specified
1324 in the ENV_FILE environment variable. If the file is not found or
1325 there is an error reading the file, an exception is raised.
1326
1327 The YAML file should have the following format:
1328
1329 .. code-block:: yaml
1330
1331 VAR_NAME: VAR_VALUE
1332
1333 Where VAR_NAME is the name of the environment variable, and
1334 VAR_VALUE is its value.
1335
1336 After loading the environment variables, they are stored in the
1337 self.env attribute for later use.
George Keishinge1686752021-07-27 12:55:28 -05001338 """
George Keishing0e9b5ba2025-05-08 12:17:58 +05301339
George Keishing7bc5ce32025-05-19 19:15:20 +05301340 tmp_env_vars = {
1341 "hostname": self.hostname,
1342 "username": self.username,
1343 "password": self.password,
1344 "port_ssh": self.port_ssh,
1345 "port_https": self.port_https,
1346 "port_ipmi": self.port_ipmi,
1347 }
George Keishinge1686752021-07-27 12:55:28 -05001348
George Keishing7bc5ce32025-05-19 19:15:20 +05301349 # Updatae default Env and Dict var for both so that it can be
1350 # verified when referencing it throughout the code.
1351 for key, value in tmp_env_vars.items():
1352 os.environ[key] = value
1353 self.env_dict[key] = value
George Keishinge1686752021-07-27 12:55:28 -05001354
1355 try:
1356 tmp_env_dict = {}
1357 if self.env_vars:
1358 tmp_env_dict = json.loads(self.env_vars)
1359 # Export ENV vars default.
1360 for key, value in tmp_env_dict.items():
1361 os.environ[key] = value
1362 self.env_dict[key] = str(value)
1363
George Keishing0e9b5ba2025-05-08 12:17:58 +05301364 # Load user specified ENV config YAML.
George Keishinge1686752021-07-27 12:55:28 -05001365 if self.econfig:
Patrick Williams20f38712022-12-08 06:18:26 -06001366 with open(self.econfig, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -05001367 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -07001368 tmp_env_dict = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -05001369 except yaml.YAMLError as e:
1370 self.logger.error(e)
1371 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001372 # Export ENV vars.
Patrick Williams20f38712022-12-08 06:18:26 -06001373 for key, value in tmp_env_dict["env_params"].items():
George Keishinge1686752021-07-27 12:55:28 -05001374 os.environ[key] = str(value)
1375 self.env_dict[key] = str(value)
1376 except json.decoder.JSONDecodeError as e:
1377 self.logger.error("\n\tERROR: %s " % e)
1378 sys.exit(-1)
George Keishing0e9b5ba2025-05-08 12:17:58 +05301379 except FileNotFoundError as e:
1380 self.logger.error("\n\tERROR: %s " % e)
1381 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001382
1383 # This to mask the password from displaying on the console.
1384 mask_dict = self.env_dict.copy()
1385 for k, v in mask_dict.items():
1386 if k.lower().find("password") != -1:
1387 hidden_text = []
1388 hidden_text.append(v)
Patrick Williams20f38712022-12-08 06:18:26 -06001389 password_regex = (
1390 "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1391 )
George Keishinge1686752021-07-27 12:55:28 -05001392 mask_dict[k] = re.sub(password_regex, "********", v)
1393
1394 self.logger.info(json.dumps(mask_dict, indent=8, sort_keys=False))
George Keishingb97a9042021-07-29 07:41:20 -05001395
George Keishingb97a9042021-07-29 07:41:20 -05001396 def execute_plugin_block(self, plugin_cmd_list):
1397 r"""
George Keishing04416092025-05-17 18:47:10 +05301398 Pack the plugin commands into qualified Python string objects.
George Keishingb97a9042021-07-29 07:41:20 -05001399
George Keishing04416092025-05-17 18:47:10 +05301400 This method processes the plugin_cmd_list argument, which is expected
1401 to contain a list of plugin commands read from a YAML file. The method
1402 iterates through the list, constructs a qualified Python string object
1403 for each plugin command, and returns a list of these string objects.
George Keishingb97a9042021-07-29 07:41:20 -05001404
George Keishing04416092025-05-17 18:47:10 +05301405 Parameters:
1406 plugin_cmd_list (list): A list of plugin commands containing
1407 plugin names and arguments.
1408 Plugin block read from YAML
1409 [
1410 {'plugin_name':'plugin.foo_func.my_func'},
1411 {'plugin_args':[10]},
1412 ]
George Keishingb97a9042021-07-29 07:41:20 -05001413
George Keishing04416092025-05-17 18:47:10 +05301414 Example:
1415 Execute and no return response
1416 - plugin:
1417 - plugin_name: plugin.foo_func.my_func
1418 - plugin_args:
1419 - arg1
1420 - arg2
George Keishingb97a9042021-07-29 07:41:20 -05001421
George Keishing04416092025-05-17 18:47:10 +05301422 Execute and return a response
1423 - plugin:
1424 - plugin_name: result = plugin.foo_func.my_func
1425 - plugin_args:
1426 - arg1
1427 - arg2
1428
1429 Execute and return multiple values response
1430 - plugin:
1431 - plugin_name: result1,result2 = plugin.foo_func.my_func
1432 - plugin_args:
1433 - arg1
1434 - arg2
1435
1436 Returns:
1437 str: Execute and not response or a string value(s) responses,
1438
George Keishingb97a9042021-07-29 07:41:20 -05001439 """
George Keishing7bc5ce32025-05-19 19:15:20 +05301440
1441 # Declare a variable plugin resp that can accept any data type.
1442 resp: Any = ""
1443 args_string = ""
1444
George Keishingb97a9042021-07-29 07:41:20 -05001445 try:
Patrick Williams20f38712022-12-08 06:18:26 -06001446 idx = self.key_index_list_dict("plugin_name", plugin_cmd_list)
George Keishing7bc5ce32025-05-19 19:15:20 +05301447 # Get plugin module name
Patrick Williams20f38712022-12-08 06:18:26 -06001448 plugin_name = plugin_cmd_list[idx]["plugin_name"]
George Keishing7bc5ce32025-05-19 19:15:20 +05301449
1450 # Get plugin function name
1451 idx = self.key_index_list_dict("plugin_function", plugin_cmd_list)
1452 plugin_function = plugin_cmd_list[idx]["plugin_function"]
1453
George Keishingb97a9042021-07-29 07:41:20 -05001454 # Equal separator means plugin function returns result.
George Keishing7bc5ce32025-05-19 19:15:20 +05301455 if " = " in plugin_function:
George Keishingb97a9042021-07-29 07:41:20 -05001456 # Ex. ['result', 'plugin.foo_func.my_func']
George Keishing7bc5ce32025-05-19 19:15:20 +05301457 plugin_function_args = plugin_function.split(" = ")
George Keishingb97a9042021-07-29 07:41:20 -05001458 # plugin func return data.
George Keishing7bc5ce32025-05-19 19:15:20 +05301459 for arg in plugin_function_args:
1460 if arg == plugin_function_args[-1]:
1461 plugin_function = arg
George Keishingb97a9042021-07-29 07:41:20 -05001462 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001463 plugin_resp = arg.split(",")
George Keishingb97a9042021-07-29 07:41:20 -05001464 # ['result1','result2']
1465 for x in plugin_resp:
1466 global_plugin_list.append(x)
1467 global_plugin_dict[x] = ""
1468
1469 # Walk the plugin args ['arg1,'arg2']
1470 # If the YAML plugin statement 'plugin_args' is not declared.
George Keishingf0eb1d62025-05-14 15:07:02 +05301471 plugin_args = []
Patrick Williams20f38712022-12-08 06:18:26 -06001472 if any("plugin_args" in d for d in plugin_cmd_list):
1473 idx = self.key_index_list_dict("plugin_args", plugin_cmd_list)
George Keishingf0eb1d62025-05-14 15:07:02 +05301474 if idx is not None:
1475 plugin_args = plugin_cmd_list[idx].get("plugin_args", [])
George Keishingb97a9042021-07-29 07:41:20 -05001476 plugin_args = self.yaml_args_populate(plugin_args)
1477 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301478 plugin_args = self.yaml_args_populate([])
George Keishingb97a9042021-07-29 07:41:20 -05001479
George Keishing473643e2025-05-22 23:54:06 +05301480 plugin_args = self.update_vars_with_env_values(
1481 global_plugin_dict, plugin_args
1482 )
George Keishing450f92f2025-05-15 23:12:51 +05301483
1484 """
1485 Example of plugin_func:
1486 plugin.redfish.enumerate_request(
1487 "xx.xx.xx.xx:443",
1488 "root",
1489 "********",
1490 "/redfish/v1/",
1491 "json")
1492 """
George Keishing7bc5ce32025-05-19 19:15:20 +05301493 # For logging purpose to mask password.
1494 # List should be string element to join else gives TypeError
1495 args_string = self.print_plugin_args_string(plugin_args)
George Keishingb97a9042021-07-29 07:41:20 -05001496
George Keishing7bc5ce32025-05-19 19:15:20 +05301497 # If user wants to debug plugins.
1498 self.logger.debug(
1499 f"\tDebug Plugin function: \n\t\t{plugin_name}."
1500 f"{plugin_function}{args_string}"
1501 )
1502
1503 # For generic logging plugin info.
1504 self.logger.info(
1505 f"\tPlugin function: \n\t\t{plugin_name}."
1506 f"{plugin_function}()"
1507 )
1508
1509 # Execute the plugins function with args.
1510 resp = execute_python_function(
1511 plugin_name, plugin_function, *plugin_args
1512 )
1513 self.logger.info(f"\tPlugin response = {resp}")
1514 # Update plugin vars dict if there is any.
1515 if resp != "PLUGIN_EXEC_ERROR":
1516 self.process_response_args_data(resp)
George Keishingb97a9042021-07-29 07:41:20 -05001517 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001518 # Set the plugin error state.
George Keishing7bc5ce32025-05-19 19:15:20 +05301519 global_plugin_error_dict["exit_on_error"] = True
George Keishing1e7b0182021-08-06 14:05:54 -05001520 self.logger.error("\tERROR: execute_plugin_block: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001521 pass
1522
George Keishing73b95d12021-08-13 14:30:52 -05001523 # There is a real error executing the plugin function.
George Keishing7bc5ce32025-05-19 19:15:20 +05301524 if resp == "PLUGIN_EXEC_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001525 return resp
1526
George Keishingde79a9b2021-08-12 16:14:43 -05001527 # Check if plugin_expects_return (int, string, list,dict etc)
Patrick Williams20f38712022-12-08 06:18:26 -06001528 if any("plugin_expects_return" in d for d in plugin_cmd_list):
1529 idx = self.key_index_list_dict(
1530 "plugin_expects_return", plugin_cmd_list
1531 )
1532 plugin_expects = plugin_cmd_list[idx]["plugin_expects_return"]
George Keishingde79a9b2021-08-12 16:14:43 -05001533 if plugin_expects:
1534 if resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001535 if (
1536 self.plugin_expect_type(plugin_expects, resp)
1537 == "INVALID"
1538 ):
George Keishingde79a9b2021-08-12 16:14:43 -05001539 self.logger.error("\tWARN: Plugin error check skipped")
1540 elif not self.plugin_expect_type(plugin_expects, resp):
Patrick Williams20f38712022-12-08 06:18:26 -06001541 self.logger.error(
1542 "\tERROR: Plugin expects return data: %s"
1543 % plugin_expects
1544 )
George Keishing7bc5ce32025-05-19 19:15:20 +05301545 global_plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001546 elif not resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001547 self.logger.error(
1548 "\tERROR: Plugin func failed to return data"
1549 )
George Keishing7bc5ce32025-05-19 19:15:20 +05301550 global_plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001551
1552 return resp
1553
George Keishing473643e2025-05-22 23:54:06 +05301554 def update_vars_with_env_values(self, ref_dict, args_list):
1555 r"""
1556 Update list elements with environment or gloable variable values.
1557
1558 This method updates the list arguments in the provided list with the
1559 corresponding values from the reference dictionary.
1560
1561 The method iterates through the dictionary and checks if each of the
1562 key matches an element in the list. If a match is found, the method
1563 replaces the key with its corresponding value in the list element. If
1564 the value is a string, the method replaces the key in the list element.
1565 If the value is not a string, the method assigns the value to the list
1566 element.
1567 The method handles exceptions and continues processing the remaining
1568 elements in the list.
1569
1570 Example:
1571
1572 Input (dict, list):
1573 {'global_log_store_path': 'LOG_PATH/BMC/system_20250523-000337'}
1574 ['ls global_log_store_path/*.txt']
1575
1576 Output(list):
1577 ['ls LOG_PATH/BMC/system_20250523-000337/*.txt']
1578
1579 Parameters:
1580 ref_dict (dict): A dictionary containing the environment or
1581 global variable values.
1582 args_list (list): A list of arguments to update.
1583
1584 Returns:
1585 list: The update list with global variables values.
1586 """
1587 # Replace keys in the string with their corresponding
1588 # values from the dictionary.
1589 for key, value in ref_dict.items():
1590 # Iterate through the list and check if each element matched
1591 # exact or in the string. If matches update the plugin element
1592 # in the list.
1593 for index, element in enumerate(args_list):
1594 try:
1595 if isinstance(element, str):
1596 # If the key is not in the list element string,
1597 # then continue for the next element in the list.
1598 if str(key) not in str(element):
1599 continue
1600 if isinstance(value, str):
1601 args_list[index] = element.replace(key, value)
1602 else:
1603 args_list[index] = ref_dict[element]
1604 except KeyError as e:
1605 print(f"Exception {e}")
1606 pass
1607 return args_list
1608
George Keishing7bc5ce32025-05-19 19:15:20 +05301609 def print_plugin_args_string(self, plugin_args):
George Keishingb97a9042021-07-29 07:41:20 -05001610 r"""
George Keishing7bc5ce32025-05-19 19:15:20 +05301611 Generate a string representation of plugin arguments, replacing the
1612 password if necessary.
George Keishingb97a9042021-07-29 07:41:20 -05001613
George Keishing7bc5ce32025-05-19 19:15:20 +05301614 This method generates a string representation of the provided plugin
1615 arguments, joining them with commas. If the password is present in the
1616 arguments, it is replaced with "********".
1617 The method returns the generated string. If an exception occurs during
1618 the process, the method logs a debug log and returns "(None)".
1619
1620 Parameters:
1621 plugin_args (list): A list of plugin arguments.
1622
1623 Returns:
1624 str: The generated string representation of the plugin arguments.
1625 """
1626 try:
1627 plugin_args_str = "(" + ", ".join(map(str, plugin_args)) + ")"
1628 if self.password in plugin_args_str:
1629 args_string = plugin_args_str.replace(
1630 self.password, "********"
1631 )
1632 else:
1633 args_string = plugin_args_str
1634 except Exception as e:
1635 self.logger.debug("\tWARN:Print args string : %s" % e)
1636 return "(None)"
1637
1638 return args_string
1639
1640 def process_response_args_data(self, plugin_resp):
1641 r"""
1642 Parse the plugin function response and update plugin return variables.
1643
1644 This method parses the response data from a plugin function and
1645 updates the plugin return variables accordingly. The method takes the
1646 plugin_resp argument, which is expected to be the response data from a
1647 plugin function.
1648
1649 The method handles various data types (string, bytes,
1650 tuple, list, int, float) and updates the global global_plugin_dict
1651 dictionary with the parsed response data. If there is an error during
1652 the process, the method logs a warning and continues with the next
1653 plugin block execution.
1654
1655 Parameters:
1656 plugin_resp (Any): The response data from the plugin function.
1657
1658 Returns:
1659 None
George Keishingb97a9042021-07-29 07:41:20 -05001660 """
1661 resp_list = []
George Keishing5765f792021-08-02 13:08:53 -05001662 resp_data = ""
George Keishing9348b402021-08-13 12:22:35 -05001663
George Keishingb97a9042021-07-29 07:41:20 -05001664 # There is nothing to update the plugin response.
Patrick Williams20f38712022-12-08 06:18:26 -06001665 if len(global_plugin_list) == 0 or plugin_resp == "None":
George Keishingb97a9042021-07-29 07:41:20 -05001666 return
1667
George Keishing5765f792021-08-02 13:08:53 -05001668 if isinstance(plugin_resp, str):
Patrick Williams20f38712022-12-08 06:18:26 -06001669 resp_data = plugin_resp.strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001670 resp_list.append(resp_data)
1671 elif isinstance(plugin_resp, bytes):
Patrick Williams20f38712022-12-08 06:18:26 -06001672 resp_data = str(plugin_resp, "UTF-8").strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001673 resp_list.append(resp_data)
1674 elif isinstance(plugin_resp, tuple):
1675 if len(global_plugin_list) == 1:
George Keishing7bc5ce32025-05-19 19:15:20 +05301676 resp_list.append(list(plugin_resp))
George Keishing5765f792021-08-02 13:08:53 -05001677 else:
1678 resp_list = list(plugin_resp)
George Keishing7bc5ce32025-05-19 19:15:20 +05301679 resp_list = [x for x in resp_list]
George Keishingb97a9042021-07-29 07:41:20 -05001680 elif isinstance(plugin_resp, list):
George Keishing5765f792021-08-02 13:08:53 -05001681 if len(global_plugin_list) == 1:
Patrick Williams20f38712022-12-08 06:18:26 -06001682 resp_list.append([x.strip("\r\n\t") for x in plugin_resp])
George Keishing5765f792021-08-02 13:08:53 -05001683 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001684 resp_list = [x.strip("\r\n\t") for x in plugin_resp]
George Keishing5765f792021-08-02 13:08:53 -05001685 elif isinstance(plugin_resp, int) or isinstance(plugin_resp, float):
1686 resp_list.append(plugin_resp)
George Keishingb97a9042021-07-29 07:41:20 -05001687
George Keishing9348b402021-08-13 12:22:35 -05001688 # Iterate if there is a list of plugin return vars to update.
George Keishingb97a9042021-07-29 07:41:20 -05001689 for idx, item in enumerate(resp_list, start=0):
George Keishing9348b402021-08-13 12:22:35 -05001690 # Exit loop, done required loop.
George Keishingb97a9042021-07-29 07:41:20 -05001691 if idx >= len(global_plugin_list):
1692 break
1693 # Find the index of the return func in the list and
1694 # update the global func return dictionary.
1695 try:
1696 dict_idx = global_plugin_list[idx]
1697 global_plugin_dict[dict_idx] = item
1698 except (IndexError, ValueError) as e:
George Keishing7bc5ce32025-05-19 19:15:20 +05301699 self.logger.warn("\tWARN: process_response_args_data: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001700 pass
1701
1702 # Done updating plugin dict irrespective of pass or failed,
George Keishing9348b402021-08-13 12:22:35 -05001703 # clear all the list element for next plugin block execute.
George Keishingb97a9042021-07-29 07:41:20 -05001704 global_plugin_list.clear()
1705
George Keishingb97a9042021-07-29 07:41:20 -05001706 def yaml_args_populate(self, yaml_arg_list):
1707 r"""
George Keishingf0eb1d62025-05-14 15:07:02 +05301708 Decode environment and plugin variables and populate the argument list.
George Keishingb97a9042021-07-29 07:41:20 -05001709
George Keishingf0eb1d62025-05-14 15:07:02 +05301710 This method processes the yaml_arg_list argument, which is expected to
1711 contain a list of arguments read from a YAML file. The method iterates
1712 through the list, decodes environment and plugin variables, and
1713 returns a populated list of arguments.
George Keishingb97a9042021-07-29 07:41:20 -05001714
George Keishingf0eb1d62025-05-14 15:07:02 +05301715 .. code-block:: yaml
1716
George Keishingb97a9042021-07-29 07:41:20 -05001717 - plugin_args:
1718 - arg1
1719 - arg2
1720
George Keishingf0eb1d62025-05-14 15:07:02 +05301721 ['${hostname}:${port_https}', '${username}', '/redfish/v1/', 'json']
George Keishingb97a9042021-07-29 07:41:20 -05001722
George Keishingf0eb1d62025-05-14 15:07:02 +05301723 Returns the populated plugin list
1724 ['xx.xx.xx.xx:443', 'root', '/redfish/v1/', 'json']
1725
1726 Parameters:
1727 yaml_arg_list (list): A list of arguments containing environment
1728 and plugin variables.
1729
1730 Returns:
1731 list: A populated list of arguments with decoded environment and
1732 plugin variables.
1733 """
George Keishingb97a9042021-07-29 07:41:20 -05001734 if isinstance(yaml_arg_list, list):
George Keishingf0eb1d62025-05-14 15:07:02 +05301735 populated_list = []
George Keishingb97a9042021-07-29 07:41:20 -05001736 for arg in yaml_arg_list:
George Keishing0581cb02021-08-05 15:08:58 -05001737 if isinstance(arg, (int, float)):
George Keishingf0eb1d62025-05-14 15:07:02 +05301738 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001739 elif isinstance(arg, str):
George Keishing7bc5ce32025-05-19 19:15:20 +05301740 arg_str = self.yaml_env_and_plugin_vars_populate(arg)
George Keishingf0eb1d62025-05-14 15:07:02 +05301741 populated_list.append(arg_str)
George Keishingb97a9042021-07-29 07:41:20 -05001742 else:
George Keishingf0eb1d62025-05-14 15:07:02 +05301743 populated_list.append(arg)
George Keishingb97a9042021-07-29 07:41:20 -05001744
George Keishingf0eb1d62025-05-14 15:07:02 +05301745 return populated_list
George Keishingb97a9042021-07-29 07:41:20 -05001746
1747 def yaml_env_and_plugin_vars_populate(self, yaml_arg_str):
1748 r"""
George Keishinga593f4b2025-05-13 20:02:36 +05301749 Update environment variables and plugin variables based on the
1750 provided YAML argument string.
George Keishingb97a9042021-07-29 07:41:20 -05001751
George Keishinga593f4b2025-05-13 20:02:36 +05301752 This method processes the yaml_arg_str argument, which is expected
1753 to contain a string representing environment variables and plugin
1754 variables in the format:
George Keishingb97a9042021-07-29 07:41:20 -05001755
George Keishinga593f4b2025-05-13 20:02:36 +05301756 .. code-block:: yaml
1757
George Keishingb97a9042021-07-29 07:41:20 -05001758 - cat ${MY_VAR}
1759 - ls -AX my_plugin_var
George Keishinga593f4b2025-05-13 20:02:36 +05301760
1761 The method parses the string, extracts the variable names, and updates
1762 the corresponding environment variables and plugin variables.
1763
1764 Parameters:
1765 yaml_arg_str (str): A string containing environment and plugin
1766 variable definitions in YAML format.
1767
1768 Returns:
1769 str: The updated YAML argument string with plugin variables
1770 replaced.
George Keishingb97a9042021-07-29 07:41:20 -05001771 """
George Keishinga593f4b2025-05-13 20:02:36 +05301772
1773 # Parse and convert the Plugin YAML vars string to python vars
1774 # Example:
1775 # ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
George Keishingb97a9042021-07-29 07:41:20 -05001776 try:
George Keishingc754b432025-04-24 14:27:14 +05301777 # Example, list of matching
1778 # env vars ['username', 'password', 'hostname']
George Keishingb97a9042021-07-29 07:41:20 -05001779 # Extra escape \ for special symbols. '\$\{([^\}]+)\}' works good.
George Keishinga593f4b2025-05-13 20:02:36 +05301780 env_var_regex = r"\$\{([^\}]+)\}"
1781 env_var_names_list = re.findall(env_var_regex, yaml_arg_str)
1782
George Keishing7bc5ce32025-05-19 19:15:20 +05301783 # If the list in empty [] nothing to update.
1784 if not len(env_var_names_list):
1785 return yaml_arg_str
George Keishingb97a9042021-07-29 07:41:20 -05001786 for var in env_var_names_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301787 env_var = os.environ.get(var)
1788 if env_var:
1789 env_replace = "${" + var + "}"
1790 yaml_arg_str = yaml_arg_str.replace(env_replace, env_var)
George Keishingb97a9042021-07-29 07:41:20 -05001791 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001792 self.logger.error("\tERROR:yaml_env_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001793 pass
1794
George Keishinga593f4b2025-05-13 20:02:36 +05301795 """
1796 Parse the string for plugin vars.
1797 Implement the logic to update environment variables based on the
1798 extracted variable names.
1799 """
George Keishingb97a9042021-07-29 07:41:20 -05001800 try:
George Keishinga593f4b2025-05-13 20:02:36 +05301801 # Example, list of plugin vars env_var_names_list
1802 # ['my_hostname', 'port_https']
1803 global_plugin_dict_keys = set(global_plugin_dict.keys())
1804 # Skip env var list already populated above code block list.
1805 plugin_var_name_list = [
1806 var
1807 for var in global_plugin_dict_keys
1808 if var not in env_var_names_list
1809 ]
1810
George Keishingb97a9042021-07-29 07:41:20 -05001811 for var in plugin_var_name_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301812 plugin_var_value = global_plugin_dict[var]
George Keishing0581cb02021-08-05 15:08:58 -05001813 if yaml_arg_str in global_plugin_dict:
George Keishinga593f4b2025-05-13 20:02:36 +05301814 """
1815 If this plugin var exist but empty in dict, don't replace.
1816 his is either a YAML plugin statement incorrectly used or
1817 user added a plugin var which is not going to be populated.
1818 """
1819 if isinstance(plugin_var_value, (list, dict)):
1820 """
1821 List data type or dict can't be replaced, use
George Keishing7bc5ce32025-05-19 19:15:20 +05301822 directly in plugin function call.
George Keishinga593f4b2025-05-13 20:02:36 +05301823 """
George Keishing0581cb02021-08-05 15:08:58 -05001824 global_plugin_type_list.append(var)
1825 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001826 yaml_arg_str = yaml_arg_str.replace(
George Keishinga593f4b2025-05-13 20:02:36 +05301827 str(var), str(plugin_var_value)
Patrick Williams20f38712022-12-08 06:18:26 -06001828 )
George Keishingb97a9042021-07-29 07:41:20 -05001829 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001830 self.logger.error("\tERROR: yaml_plugin_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001831 pass
1832
George Keishinga593f4b2025-05-13 20:02:36 +05301833 # From ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
1834 # to populated values string as
1835 # Example: xx.xx.xx.xx:443 and return the string
George Keishingb97a9042021-07-29 07:41:20 -05001836 return yaml_arg_str
George Keishing1e7b0182021-08-06 14:05:54 -05001837
1838 def plugin_error_check(self, plugin_dict):
1839 r"""
George Keishing1e877422025-05-09 20:45:09 +05301840 Process plugin error dictionary and return the corresponding error
1841 message.
George Keishing1e7b0182021-08-06 14:05:54 -05001842
George Keishing1e877422025-05-09 20:45:09 +05301843 This method checks if any dictionary in the plugin_dict list contains
1844 a "plugin_error" key. If such a dictionary is found, it retrieves the
1845 value associated with the "plugin_error" key and returns the
George Keishing7bc5ce32025-05-19 19:15:20 +05301846 corresponding error message from the global_plugin_error_dict
1847 attribute.
George Keishing1e877422025-05-09 20:45:09 +05301848
1849 Parameters:
1850 plugin_dict (list of dict): A list of dictionaries containing
1851 plugin error information.
1852
1853 Returns:
1854 str: The error message corresponding to the "plugin_error" value,
1855 or None if no error is found.
George Keishing1e7b0182021-08-06 14:05:54 -05001856 """
Patrick Williams20f38712022-12-08 06:18:26 -06001857 if any("plugin_error" in d for d in plugin_dict):
George Keishing1e7b0182021-08-06 14:05:54 -05001858 for d in plugin_dict:
Patrick Williams20f38712022-12-08 06:18:26 -06001859 if "plugin_error" in d:
1860 value = d["plugin_error"]
George Keishing7bc5ce32025-05-19 19:15:20 +05301861 return global_plugin_error_dict.get(value, None)
George Keishing1e877422025-05-09 20:45:09 +05301862 return None
George Keishingde79a9b2021-08-12 16:14:43 -05001863
1864 def key_index_list_dict(self, key, list_dict):
1865 r"""
George Keishing1e877422025-05-09 20:45:09 +05301866 Find the index of the first dictionary in the list that contains
1867 the specified key.
George Keishingde79a9b2021-08-12 16:14:43 -05001868
George Keishing1e877422025-05-09 20:45:09 +05301869 Parameters:
1870 key (str): The key to search for in the
1871 dictionaries.
1872 list_dict (list of dict): A list of dictionaries to search
1873 through.
1874
1875 Returns:
1876 int: The index of the first dictionary containing the key, or -1
1877 if no match is found.
George Keishingde79a9b2021-08-12 16:14:43 -05001878 """
1879 for i, d in enumerate(list_dict):
George Keishing1e877422025-05-09 20:45:09 +05301880 if key in d:
George Keishingde79a9b2021-08-12 16:14:43 -05001881 return i
George Keishing1e877422025-05-09 20:45:09 +05301882 return -1
George Keishingde79a9b2021-08-12 16:14:43 -05001883
1884 def plugin_expect_type(self, type, data):
1885 r"""
George Keishing1e877422025-05-09 20:45:09 +05301886 Check if the provided data matches the expected type.
1887
1888 This method checks if the data argument matches the specified type.
1889 It supports the following types: "int", "float", "str", "list", "dict",
1890 and "tuple".
1891
1892 If the type is not recognized, it logs an info message and returns
1893 "INVALID".
1894
1895 Parameters:
1896 type (str): The expected data type.
1897 data: The data to check against the expected type.
1898
1899 Returns:
1900 bool or str: True if the data matches the expected type, False if
1901 not, or "INVALID" if the type is not recognized.
George Keishingde79a9b2021-08-12 16:14:43 -05001902 """
Patrick Williams20f38712022-12-08 06:18:26 -06001903 if type == "int":
George Keishingde79a9b2021-08-12 16:14:43 -05001904 return isinstance(data, int)
Patrick Williams20f38712022-12-08 06:18:26 -06001905 elif type == "float":
George Keishingde79a9b2021-08-12 16:14:43 -05001906 return isinstance(data, float)
Patrick Williams20f38712022-12-08 06:18:26 -06001907 elif type == "str":
George Keishingde79a9b2021-08-12 16:14:43 -05001908 return isinstance(data, str)
Patrick Williams20f38712022-12-08 06:18:26 -06001909 elif type == "list":
George Keishingde79a9b2021-08-12 16:14:43 -05001910 return isinstance(data, list)
Patrick Williams20f38712022-12-08 06:18:26 -06001911 elif type == "dict":
George Keishingde79a9b2021-08-12 16:14:43 -05001912 return isinstance(data, dict)
Patrick Williams20f38712022-12-08 06:18:26 -06001913 elif type == "tuple":
George Keishingde79a9b2021-08-12 16:14:43 -05001914 return isinstance(data, tuple)
1915 else:
1916 self.logger.info("\tInvalid data type requested: %s" % type)
Patrick Williams20f38712022-12-08 06:18:26 -06001917 return "INVALID"