blob: d94464f50f207789417d7fa9e676cf9038462cd1 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Peter D Phan72ce6b82021-06-03 06:18:26 -05002
3r"""
4See class prolog below for details.
5"""
6
Patrick Williams20f38712022-12-08 06:18:26 -06007import json
8import logging
9import os
10import platform
11import re
12import subprocess
13import sys
14import time
George Keishing09679892022-12-08 08:21:52 -060015from errno import EACCES, EPERM
16
George Keishinge635ddc2022-12-08 07:38:02 -060017import yaml
Peter D Phan5e56f522021-12-20 13:19:41 -060018
George Keishing168545d2025-05-12 19:26:54 +053019sys.dont_write_bytecode = True
20
21
Peter D Phancb791d72022-02-08 12:23:03 -060022script_dir = os.path.dirname(os.path.abspath(__file__))
23sys.path.append(script_dir)
24# Walk path and append to sys.path
25for root, dirs, files in os.walk(script_dir):
26 for dir in dirs:
27 sys.path.append(os.path.join(root, dir))
28
Patrick Williams20f38712022-12-08 06:18:26 -060029from ssh_utility import SSHRemoteclient # NOQA
30from telnet_utility import TelnetRemoteclient # NOQA
Peter D Phan72ce6b82021-06-03 06:18:26 -050031
George Keishingb97a9042021-07-29 07:41:20 -050032r"""
33User define plugins python functions.
34
35It will imports files from directory plugins
36
37plugins
38├── file1.py
39└── file2.py
40
41Example how to define in YAML:
42 - plugin:
43 - plugin_name: plugin.foo_func.foo_func_yaml
44 - plugin_args:
45 - arg1
46 - arg2
47"""
George Keishing0e9b5ba2025-05-08 12:17:58 +053048plugin_dir = os.path.join(os.path.dirname(__file__), "plugins")
Peter D Phan5e56f522021-12-20 13:19:41 -060049sys.path.append(plugin_dir)
George Keishing0e9b5ba2025-05-08 12:17:58 +053050
51for module in os.listdir(plugin_dir):
52 if module == "__init__.py" or not module.endswith(".py"):
53 continue
54
55 plugin_module = f"plugins.{module[:-3]}"
56 try:
57 plugin = __import__(plugin_module, globals(), locals(), [], 0)
58 except Exception as e:
59 print(f"PLUGIN: Exception: {e}")
60 print(f"PLUGIN: Module import failed: {module}")
61 continue
George Keishingb97a9042021-07-29 07:41:20 -050062
63r"""
64This is for plugin functions returning data or responses to the caller
65in YAML plugin setup.
66
67Example:
68
69 - plugin:
70 - plugin_name: version = plugin.ssh_execution.ssh_execute_cmd
71 - plugin_args:
72 - ${hostname}
73 - ${username}
74 - ${password}
75 - "cat /etc/os-release | grep VERSION_ID | awk -F'=' '{print $2}'"
76 - plugin:
77 - plugin_name: plugin.print_vars.print_vars
78 - plugin_args:
79 - version
80
81where first plugin "version" var is used by another plugin in the YAML
82block or plugin
83
84"""
85global global_log_store_path
86global global_plugin_dict
87global global_plugin_list
George Keishing9348b402021-08-13 12:22:35 -050088
George Keishing0581cb02021-08-05 15:08:58 -050089# Hold the plugin return values in dict and plugin return vars in list.
George Keishing9348b402021-08-13 12:22:35 -050090# Dict is to reference and update vars processing in parser where as
91# list is for current vars from the plugin block which needs processing.
George Keishingb97a9042021-07-29 07:41:20 -050092global_plugin_dict = {}
93global_plugin_list = []
George Keishing9348b402021-08-13 12:22:35 -050094
George Keishingc754b432025-04-24 14:27:14 +053095# Hold the plugin return named declared if function returned values are
96# list,dict.
George Keishing0581cb02021-08-05 15:08:58 -050097# Refer this name list to look up the plugin dict for eval() args function
George Keishing9348b402021-08-13 12:22:35 -050098# Example ['version']
George Keishing0581cb02021-08-05 15:08:58 -050099global_plugin_type_list = []
George Keishing9348b402021-08-13 12:22:35 -0500100
101# Path where logs are to be stored or written.
Patrick Williams20f38712022-12-08 06:18:26 -0600102global_log_store_path = ""
George Keishingb97a9042021-07-29 07:41:20 -0500103
George Keishing1e7b0182021-08-06 14:05:54 -0500104# Plugin error state defaults.
105plugin_error_dict = {
Patrick Williams20f38712022-12-08 06:18:26 -0600106 "exit_on_error": False,
107 "continue_on_error": False,
George Keishing1e7b0182021-08-06 14:05:54 -0500108}
109
Peter D Phan72ce6b82021-06-03 06:18:26 -0500110
Peter D Phan5e56f522021-12-20 13:19:41 -0600111class ffdc_collector:
Peter D Phan72ce6b82021-06-03 06:18:26 -0500112 r"""
George Keishing1e7b0182021-08-06 14:05:54 -0500113 Execute commands from configuration file to collect log files.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500114 Fetch and store generated files at the specified location.
115
116 """
117
Patrick Williams20f38712022-12-08 06:18:26 -0600118 def __init__(
119 self,
120 hostname,
121 username,
122 password,
George Keishing7a61aa22023-06-26 13:18:37 +0530123 port_ssh,
George Keishinge8a41752023-06-22 21:42:47 +0530124 port_https,
125 port_ipmi,
Patrick Williams20f38712022-12-08 06:18:26 -0600126 ffdc_config,
127 location,
128 remote_type,
129 remote_protocol,
130 env_vars,
131 econfig,
132 log_level,
133 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500134 r"""
135 Description of argument(s):
136
George Keishingc754b432025-04-24 14:27:14 +0530137 hostname Name/ip of the targeted (remote) system
138 username User on the targeted system with access to
139 FFDC files
140 password Password for user on targeted system
George Keishing7a61aa22023-06-26 13:18:37 +0530141 port_ssh SSH port value. By default 22
George Keishinge8a41752023-06-22 21:42:47 +0530142 port_https HTTPS port value. By default 443
143 port_ipmi IPMI port value. By default 623
George Keishingc754b432025-04-24 14:27:14 +0530144 ffdc_config Configuration file listing commands and files
145 for FFDC
146 location Where to store collected FFDC
147 remote_type OS type of the remote host
George Keishing8e94f8c2021-07-23 15:06:32 -0500148 remote_protocol Protocol to use to collect data
149 env_vars User define CLI env vars '{"key : "value"}'
150 econfig User define env vars YAML file
Peter D Phan72ce6b82021-06-03 06:18:26 -0500151
152 """
Peter D Phane86d9a52021-07-15 10:42:25 -0500153
154 self.hostname = hostname
155 self.username = username
156 self.password = password
George Keishing7a61aa22023-06-26 13:18:37 +0530157 self.port_ssh = str(port_ssh)
George Keishinge8a41752023-06-22 21:42:47 +0530158 self.port_https = str(port_https)
159 self.port_ipmi = str(port_ipmi)
Peter D Phane86d9a52021-07-15 10:42:25 -0500160 self.ffdc_config = ffdc_config
161 self.location = location + "/" + remote_type.upper()
162 self.ssh_remoteclient = None
163 self.telnet_remoteclient = None
164 self.ffdc_dir_path = ""
165 self.ffdc_prefix = ""
166 self.target_type = remote_type.upper()
167 self.remote_protocol = remote_protocol.upper()
George Keishinge1686752021-07-27 12:55:28 -0500168 self.env_vars = env_vars
169 self.econfig = econfig
Peter D Phane86d9a52021-07-15 10:42:25 -0500170 self.start_time = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600171 self.elapsed_time = ""
Peter D Phane86d9a52021-07-15 10:42:25 -0500172 self.logger = None
173
174 # Set prefix values for scp files and directory.
George Keishingc754b432025-04-24 14:27:14 +0530175 # Since the time stamp is at second granularity, these values are set
176 # here to be sure that all files for this run will have same timestamps
Peter D Phane86d9a52021-07-15 10:42:25 -0500177 # and they will be saved in the same directory.
178 # self.location == local system for now
Peter D Phan5e56f522021-12-20 13:19:41 -0600179 self.set_ffdc_default_store_path()
Peter D Phane86d9a52021-07-15 10:42:25 -0500180
Peter D Phan5e56f522021-12-20 13:19:41 -0600181 # Logger for this run. Need to be after set_ffdc_default_store_path()
Peter D Phane86d9a52021-07-15 10:42:25 -0500182 self.script_logging(getattr(logging, log_level.upper()))
183
184 # Verify top level directory exists for storage
185 self.validate_local_store(self.location)
186
Peter D Phan72ce6b82021-06-03 06:18:26 -0500187 if self.verify_script_env():
Peter D Phane86d9a52021-07-15 10:42:25 -0500188 # Load default or user define YAML configuration file.
Patrick Williams20f38712022-12-08 06:18:26 -0600189 with open(self.ffdc_config, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -0500190 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -0700191 self.ffdc_actions = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -0500192 except yaml.YAMLError as e:
193 self.logger.error(e)
194 sys.exit(-1)
Peter D Phane86d9a52021-07-15 10:42:25 -0500195
196 if self.target_type not in self.ffdc_actions.keys():
197 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600198 "\n\tERROR: %s is not listed in %s.\n\n"
199 % (self.target_type, self.ffdc_config)
200 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500201 sys.exit(-1)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500202 else:
Peter D Phan8462faf2021-06-16 12:24:15 -0500203 sys.exit(-1)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500204
George Keishing4885b2f2021-07-21 15:22:45 -0500205 # Load ENV vars from user.
George Keishingaa1f8482021-07-22 00:54:55 -0500206 self.logger.info("\n\tENV: User define input YAML variables")
207 self.env_dict = {}
Peter D Phan5e56f522021-12-20 13:19:41 -0600208 self.load_env()
George Keishingaa1f8482021-07-22 00:54:55 -0500209
Peter D Phan72ce6b82021-06-03 06:18:26 -0500210 def verify_script_env(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500211 # Import to log version
212 import click
213 import paramiko
214
215 run_env_ok = True
Peter D Phan0c669772021-06-24 13:52:42 -0500216
George Keishingd805bc02025-02-28 12:17:13 +0530217 try:
218 redfishtool_version = (
219 self.run_tool_cmd("redfishtool -V").split(" ")[2].strip("\n")
220 )
221 except Exception as e:
222 self.logger.error("\tEXCEPTION redfishtool: %s", e)
223 redfishtool_version = "Not Installed (optional)"
224
225 try:
226 ipmitool_version = self.run_tool_cmd("ipmitool -V").split(" ")[2]
227 except Exception as e:
228 self.logger.error("\tEXCEPTION ipmitool: %s", e)
229 ipmitool_version = "Not Installed (optional)"
Peter D Phan0c669772021-06-24 13:52:42 -0500230
Peter D Phane86d9a52021-07-15 10:42:25 -0500231 self.logger.info("\n\t---- Script host environment ----")
Patrick Williams20f38712022-12-08 06:18:26 -0600232 self.logger.info(
233 "\t{:<10} {:<10}".format("Script hostname", os.uname()[1])
234 )
235 self.logger.info(
236 "\t{:<10} {:<10}".format("Script host os", platform.platform())
237 )
238 self.logger.info(
239 "\t{:<10} {:>10}".format("Python", platform.python_version())
240 )
241 self.logger.info("\t{:<10} {:>10}".format("PyYAML", yaml.__version__))
242 self.logger.info("\t{:<10} {:>10}".format("click", click.__version__))
243 self.logger.info(
244 "\t{:<10} {:>10}".format("paramiko", paramiko.__version__)
245 )
246 self.logger.info(
247 "\t{:<10} {:>9}".format("redfishtool", redfishtool_version)
248 )
249 self.logger.info(
250 "\t{:<10} {:>12}".format("ipmitool", ipmitool_version)
251 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500252
Patrick Williams20f38712022-12-08 06:18:26 -0600253 if eval(yaml.__version__.replace(".", ",")) < (5, 3, 0):
254 self.logger.error(
255 "\n\tERROR: Python or python packages do not meet minimum"
256 " version requirement."
257 )
258 self.logger.error(
259 "\tERROR: PyYAML version 5.3.0 or higher is needed.\n"
260 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500261 run_env_ok = False
262
Peter D Phane86d9a52021-07-15 10:42:25 -0500263 self.logger.info("\t---- End script host environment ----")
Peter D Phan72ce6b82021-06-03 06:18:26 -0500264 return run_env_ok
265
Patrick Williams20f38712022-12-08 06:18:26 -0600266 def script_logging(self, log_level_attr):
Peter D Phane86d9a52021-07-15 10:42:25 -0500267 r"""
268 Create logger
269
270 """
271 self.logger = logging.getLogger()
272 self.logger.setLevel(log_level_attr)
Patrick Williams20f38712022-12-08 06:18:26 -0600273 log_file_handler = logging.FileHandler(
274 self.ffdc_dir_path + "collector.log"
275 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500276
277 stdout_handler = logging.StreamHandler(sys.stdout)
278 self.logger.addHandler(log_file_handler)
279 self.logger.addHandler(stdout_handler)
280
281 # Turn off paramiko INFO logging
282 logging.getLogger("paramiko").setLevel(logging.WARNING)
283
Peter D Phan72ce6b82021-06-03 06:18:26 -0500284 def target_is_pingable(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500285 r"""
286 Check if target system is ping-able.
287
288 """
George Keishing0662e942021-07-13 05:12:20 -0500289 response = os.system("ping -c 1 %s 2>&1 >/dev/null" % self.hostname)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500290 if response == 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600291 self.logger.info(
292 "\n\t[Check] %s is ping-able.\t\t [OK]" % self.hostname
293 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500294 return True
295 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500296 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600297 "\n\tERROR: %s is not ping-able. FFDC collection aborted.\n"
298 % self.hostname
299 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500300 sys.exit(-1)
301
Peter D Phan72ce6b82021-06-03 06:18:26 -0500302 def collect_ffdc(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500303 r"""
304 Initiate FFDC Collection depending on requested protocol.
305
306 """
307
Patrick Williams20f38712022-12-08 06:18:26 -0600308 self.logger.info(
309 "\n\t---- Start communicating with %s ----" % self.hostname
310 )
Peter D Phan7610bc42021-07-06 06:31:05 -0500311 self.start_time = time.time()
Peter D Phan0c669772021-06-24 13:52:42 -0500312
George Keishingf5a57502021-07-22 16:43:47 -0500313 # Find the list of target and protocol supported.
314 check_protocol_list = []
315 config_dict = self.ffdc_actions
Peter D Phan0c669772021-06-24 13:52:42 -0500316
George Keishingf5a57502021-07-22 16:43:47 -0500317 for target_type in config_dict.keys():
318 if self.target_type != target_type:
319 continue
George Keishingeafba182021-06-29 13:44:58 -0500320
George Keishingf5a57502021-07-22 16:43:47 -0500321 for k, v in config_dict[target_type].items():
Patrick Williams20f38712022-12-08 06:18:26 -0600322 if (
323 config_dict[target_type][k]["PROTOCOL"][0]
324 not in check_protocol_list
325 ):
326 check_protocol_list.append(
327 config_dict[target_type][k]["PROTOCOL"][0]
328 )
Peter D Phanbff617a2021-07-22 08:41:35 -0500329
Patrick Williams20f38712022-12-08 06:18:26 -0600330 self.logger.info(
331 "\n\t %s protocol type: %s"
332 % (self.target_type, check_protocol_list)
333 )
Peter D Phanbff617a2021-07-22 08:41:35 -0500334
George Keishingf5a57502021-07-22 16:43:47 -0500335 verified_working_protocol = self.verify_protocol(check_protocol_list)
Peter D Phanbff617a2021-07-22 08:41:35 -0500336
George Keishingf5a57502021-07-22 16:43:47 -0500337 if verified_working_protocol:
Patrick Williams20f38712022-12-08 06:18:26 -0600338 self.logger.info(
339 "\n\t---- Completed protocol pre-requisite check ----\n"
340 )
Peter D Phan0c669772021-06-24 13:52:42 -0500341
George Keishingf5a57502021-07-22 16:43:47 -0500342 # Verify top level directory exists for storage
343 self.validate_local_store(self.location)
344
Patrick Williams20f38712022-12-08 06:18:26 -0600345 if (self.remote_protocol not in verified_working_protocol) and (
346 self.remote_protocol != "ALL"
347 ):
348 self.logger.info(
349 "\n\tWorking protocol list: %s" % verified_working_protocol
350 )
George Keishingf5a57502021-07-22 16:43:47 -0500351 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600352 "\tERROR: Requested protocol %s is not in working protocol"
George Keishing7899a452023-02-15 02:46:54 -0600353 " list.\n" % self.remote_protocol
Patrick Williams20f38712022-12-08 06:18:26 -0600354 )
George Keishingf5a57502021-07-22 16:43:47 -0500355 sys.exit(-1)
356 else:
357 self.generate_ffdc(verified_working_protocol)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500358
359 def ssh_to_target_system(self):
360 r"""
361 Open a ssh connection to targeted system.
362
363 """
364
Patrick Williams20f38712022-12-08 06:18:26 -0600365 self.ssh_remoteclient = SSHRemoteclient(
George Keishing7a61aa22023-06-26 13:18:37 +0530366 self.hostname, self.username, self.password, self.port_ssh
Patrick Williams20f38712022-12-08 06:18:26 -0600367 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500368
Peter D Phan5963d632021-07-12 09:58:55 -0500369 if self.ssh_remoteclient.ssh_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600370 self.logger.info(
371 "\n\t[Check] %s SSH connection established.\t [OK]"
372 % self.hostname
373 )
Peter D Phan733df632021-06-17 13:13:36 -0500374
Peter D Phan5963d632021-07-12 09:58:55 -0500375 # Check scp connection.
376 # If scp connection fails,
377 # continue with FFDC generation but skip scp files to local host.
378 self.ssh_remoteclient.scp_connection()
379 return True
380 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600381 self.logger.info(
382 "\n\t[Check] %s SSH connection.\t [NOT AVAILABLE]"
383 % self.hostname
384 )
Peter D Phan5963d632021-07-12 09:58:55 -0500385 return False
386
387 def telnet_to_target_system(self):
388 r"""
389 Open a telnet connection to targeted system.
390 """
Patrick Williams20f38712022-12-08 06:18:26 -0600391 self.telnet_remoteclient = TelnetRemoteclient(
392 self.hostname, self.username, self.password
393 )
Peter D Phan5963d632021-07-12 09:58:55 -0500394 if self.telnet_remoteclient.tn_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600395 self.logger.info(
396 "\n\t[Check] %s Telnet connection established.\t [OK]"
397 % self.hostname
398 )
Peter D Phan5963d632021-07-12 09:58:55 -0500399 return True
400 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600401 self.logger.info(
402 "\n\t[Check] %s Telnet connection.\t [NOT AVAILABLE]"
403 % self.hostname
404 )
Peter D Phan5963d632021-07-12 09:58:55 -0500405 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500406
George Keishing772c9772021-06-16 23:23:42 -0500407 def generate_ffdc(self, working_protocol_list):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500408 r"""
Peter D Phan04aca3b2021-06-21 10:37:18 -0500409 Determine actions based on remote host type
Peter D Phan72ce6b82021-06-03 06:18:26 -0500410
Peter D Phan04aca3b2021-06-21 10:37:18 -0500411 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530412 working_protocol_list List of confirmed working protocols to
413 connect to remote host.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500414 """
415
Patrick Williams20f38712022-12-08 06:18:26 -0600416 self.logger.info(
417 "\n\t---- Executing commands on " + self.hostname + " ----"
418 )
419 self.logger.info(
420 "\n\tWorking protocol list: %s" % working_protocol_list
421 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500422
George Keishingf5a57502021-07-22 16:43:47 -0500423 config_dict = self.ffdc_actions
424 for target_type in config_dict.keys():
425 if self.target_type != target_type:
George Keishing6ea92b02021-07-01 11:20:50 -0500426 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500427
Peter D Phane86d9a52021-07-15 10:42:25 -0500428 self.logger.info("\n\tFFDC Path: %s " % self.ffdc_dir_path)
Patrick Williams20f38712022-12-08 06:18:26 -0600429 global_plugin_dict["global_log_store_path"] = self.ffdc_dir_path
George Keishingf5a57502021-07-22 16:43:47 -0500430 self.logger.info("\tSystem Type: %s" % target_type)
431 for k, v in config_dict[target_type].items():
Patrick Williams20f38712022-12-08 06:18:26 -0600432 if (
433 self.remote_protocol not in working_protocol_list
434 and self.remote_protocol != "ALL"
435 ):
George Keishing6ea92b02021-07-01 11:20:50 -0500436 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500437
Patrick Williams20f38712022-12-08 06:18:26 -0600438 protocol = config_dict[target_type][k]["PROTOCOL"][0]
George Keishingf5a57502021-07-22 16:43:47 -0500439
440 if protocol not in working_protocol_list:
441 continue
442
George Keishingb7607612021-07-27 13:31:23 -0500443 if protocol in working_protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -0600444 if protocol == "SSH" or protocol == "SCP":
George Keishing12fd0652021-07-27 13:57:11 -0500445 self.protocol_ssh(protocol, target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600446 elif protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -0500447 self.protocol_telnet(target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600448 elif (
449 protocol == "REDFISH"
450 or protocol == "IPMI"
451 or protocol == "SHELL"
452 ):
George Keishing506b0582021-07-27 09:31:22 -0500453 self.protocol_execute(protocol, target_type, k)
George Keishingb7607612021-07-27 13:31:23 -0500454 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600455 self.logger.error(
456 "\n\tERROR: %s is not available for %s."
457 % (protocol, self.hostname)
458 )
George Keishingeafba182021-06-29 13:44:58 -0500459
Peter D Phan04aca3b2021-06-21 10:37:18 -0500460 # Close network connection after collecting all files
Patrick Williams20f38712022-12-08 06:18:26 -0600461 self.elapsed_time = time.strftime(
462 "%H:%M:%S", time.gmtime(time.time() - self.start_time)
463 )
George Keishing48972ba2025-05-05 17:40:29 +0530464 self.logger.info("\n\tTotal time taken: %s" % self.elapsed_time)
Peter D Phanbff617a2021-07-22 08:41:35 -0500465 if self.ssh_remoteclient:
466 self.ssh_remoteclient.ssh_remoteclient_disconnect()
467 if self.telnet_remoteclient:
468 self.telnet_remoteclient.tn_remoteclient_disconnect()
Peter D Phan04aca3b2021-06-21 10:37:18 -0500469
Patrick Williams20f38712022-12-08 06:18:26 -0600470 def protocol_ssh(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500471 r"""
472 Perform actions using SSH and SCP protocols.
473
474 Description of argument(s):
George Keishing12fd0652021-07-27 13:57:11 -0500475 protocol Protocol to execute.
George Keishingf5a57502021-07-22 16:43:47 -0500476 target_type OS Type of remote host.
George Keishing6ea92b02021-07-01 11:20:50 -0500477 sub_type Group type of commands.
Peter D Phan0c669772021-06-24 13:52:42 -0500478 """
479
Patrick Williams20f38712022-12-08 06:18:26 -0600480 if protocol == "SCP":
George Keishingf5a57502021-07-22 16:43:47 -0500481 self.group_copy(self.ffdc_actions[target_type][sub_type])
George Keishing6ea92b02021-07-01 11:20:50 -0500482 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600483 self.collect_and_copy_ffdc(
484 self.ffdc_actions[target_type][sub_type]
485 )
Peter D Phan0c669772021-06-24 13:52:42 -0500486
Patrick Williams20f38712022-12-08 06:18:26 -0600487 def protocol_telnet(self, target_type, sub_type):
Peter D Phan5963d632021-07-12 09:58:55 -0500488 r"""
489 Perform actions using telnet protocol.
490 Description of argument(s):
George Keishingf5a57502021-07-22 16:43:47 -0500491 target_type OS Type of remote host.
Peter D Phan5963d632021-07-12 09:58:55 -0500492 """
Patrick Williams20f38712022-12-08 06:18:26 -0600493 self.logger.info(
494 "\n\t[Run] Executing commands on %s using %s"
495 % (self.hostname, "TELNET")
496 )
Peter D Phan5963d632021-07-12 09:58:55 -0500497 telnet_files_saved = []
498 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600499 list_of_commands = self.ffdc_actions[target_type][sub_type]["COMMANDS"]
Peter D Phan5963d632021-07-12 09:58:55 -0500500 for index, each_cmd in enumerate(list_of_commands, start=0):
501 command_txt, command_timeout = self.unpack_command(each_cmd)
Patrick Williams20f38712022-12-08 06:18:26 -0600502 result = self.telnet_remoteclient.execute_command(
503 command_txt, command_timeout
504 )
Peter D Phan5963d632021-07-12 09:58:55 -0500505 if result:
506 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600507 targ_file = self.ffdc_actions[target_type][sub_type][
508 "FILES"
509 ][index]
Peter D Phan5963d632021-07-12 09:58:55 -0500510 except IndexError:
Peter D Phane86d9a52021-07-15 10:42:25 -0500511 targ_file = command_txt
512 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600513 "\n\t[WARN] Missing filename to store data from"
514 " telnet %s." % each_cmd
515 )
516 self.logger.warning(
517 "\t[WARN] Data will be stored in %s." % targ_file
518 )
519 targ_file_with_path = (
520 self.ffdc_dir_path + self.ffdc_prefix + targ_file
521 )
Peter D Phan5963d632021-07-12 09:58:55 -0500522 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600523 with open(targ_file_with_path, "w") as fp:
Peter D Phan5963d632021-07-12 09:58:55 -0500524 fp.write(result)
525 fp.close
526 telnet_files_saved.append(targ_file)
527 progress_counter += 1
528 self.print_progress(progress_counter)
Peter D Phane86d9a52021-07-15 10:42:25 -0500529 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan5963d632021-07-12 09:58:55 -0500530 for file in telnet_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500531 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
Peter D Phan5963d632021-07-12 09:58:55 -0500532
Patrick Williams20f38712022-12-08 06:18:26 -0600533 def protocol_execute(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500534 r"""
George Keishing506b0582021-07-27 09:31:22 -0500535 Perform actions for a given protocol.
Peter D Phan0c669772021-06-24 13:52:42 -0500536
537 Description of argument(s):
George Keishing506b0582021-07-27 09:31:22 -0500538 protocol Protocol to execute.
George Keishingf5a57502021-07-22 16:43:47 -0500539 target_type OS Type of remote host.
George Keishing6ea92b02021-07-01 11:20:50 -0500540 sub_type Group type of commands.
Peter D Phan0c669772021-06-24 13:52:42 -0500541 """
542
Patrick Williams20f38712022-12-08 06:18:26 -0600543 self.logger.info(
544 "\n\t[Run] Executing commands to %s using %s"
545 % (self.hostname, protocol)
546 )
George Keishing506b0582021-07-27 09:31:22 -0500547 executed_files_saved = []
George Keishingeafba182021-06-29 13:44:58 -0500548 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600549 list_of_cmd = self.get_command_list(
550 self.ffdc_actions[target_type][sub_type]
551 )
George Keishingeafba182021-06-29 13:44:58 -0500552 for index, each_cmd in enumerate(list_of_cmd, start=0):
George Keishingcaa97e62021-08-03 14:00:09 -0500553 plugin_call = False
George Keishingb97a9042021-07-29 07:41:20 -0500554 if isinstance(each_cmd, dict):
Patrick Williams20f38712022-12-08 06:18:26 -0600555 if "plugin" in each_cmd:
George Keishing1e7b0182021-08-06 14:05:54 -0500556 # If the error is set and plugin explicitly
557 # requested to skip execution on error..
Patrick Williams20f38712022-12-08 06:18:26 -0600558 if plugin_error_dict[
559 "exit_on_error"
560 ] and self.plugin_error_check(each_cmd["plugin"]):
561 self.logger.info(
562 "\n\t[PLUGIN-ERROR] exit_on_error: %s"
563 % plugin_error_dict["exit_on_error"]
564 )
565 self.logger.info(
566 "\t[PLUGIN-SKIP] %s" % each_cmd["plugin"][0]
567 )
George Keishing1e7b0182021-08-06 14:05:54 -0500568 continue
George Keishingcaa97e62021-08-03 14:00:09 -0500569 plugin_call = True
George Keishingb97a9042021-07-29 07:41:20 -0500570 # call the plugin
571 self.logger.info("\n\t[PLUGIN-START]")
Patrick Williams20f38712022-12-08 06:18:26 -0600572 result = self.execute_plugin_block(each_cmd["plugin"])
George Keishingb97a9042021-07-29 07:41:20 -0500573 self.logger.info("\t[PLUGIN-END]\n")
George Keishingb97a9042021-07-29 07:41:20 -0500574 else:
George Keishing2b83e042021-08-03 12:56:11 -0500575 each_cmd = self.yaml_env_and_plugin_vars_populate(each_cmd)
George Keishingb97a9042021-07-29 07:41:20 -0500576
George Keishingcaa97e62021-08-03 14:00:09 -0500577 if not plugin_call:
578 result = self.run_tool_cmd(each_cmd)
George Keishingeafba182021-06-29 13:44:58 -0500579 if result:
580 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600581 file_name = self.get_file_list(
582 self.ffdc_actions[target_type][sub_type]
583 )[index]
George Keishingb97a9042021-07-29 07:41:20 -0500584 # If file is specified as None.
George Keishing0581cb02021-08-05 15:08:58 -0500585 if file_name == "None":
George Keishingb97a9042021-07-29 07:41:20 -0500586 continue
Patrick Williams20f38712022-12-08 06:18:26 -0600587 targ_file = self.yaml_env_and_plugin_vars_populate(
588 file_name
589 )
George Keishingeafba182021-06-29 13:44:58 -0500590 except IndexError:
Patrick Williams20f38712022-12-08 06:18:26 -0600591 targ_file = each_cmd.split("/")[-1]
George Keishing506b0582021-07-27 09:31:22 -0500592 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600593 "\n\t[WARN] Missing filename to store data from %s."
594 % each_cmd
595 )
596 self.logger.warning(
597 "\t[WARN] Data will be stored in %s." % targ_file
598 )
George Keishingeafba182021-06-29 13:44:58 -0500599
Patrick Williams20f38712022-12-08 06:18:26 -0600600 targ_file_with_path = (
601 self.ffdc_dir_path + self.ffdc_prefix + targ_file
602 )
George Keishingeafba182021-06-29 13:44:58 -0500603
604 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600605 with open(targ_file_with_path, "w") as fp:
George Keishing91308ea2021-08-10 14:43:15 -0500606 if isinstance(result, dict):
607 fp.write(json.dumps(result))
608 else:
609 fp.write(result)
George Keishingeafba182021-06-29 13:44:58 -0500610 fp.close
George Keishing506b0582021-07-27 09:31:22 -0500611 executed_files_saved.append(targ_file)
George Keishingeafba182021-06-29 13:44:58 -0500612
613 progress_counter += 1
614 self.print_progress(progress_counter)
615
Peter D Phane86d9a52021-07-15 10:42:25 -0500616 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
George Keishingeafba182021-06-29 13:44:58 -0500617
George Keishing506b0582021-07-27 09:31:22 -0500618 for file in executed_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500619 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
George Keishingeafba182021-06-29 13:44:58 -0500620
Patrick Williams20f38712022-12-08 06:18:26 -0600621 def collect_and_copy_ffdc(
622 self, ffdc_actions_for_target_type, form_filename=False
623 ):
Peter D Phan04aca3b2021-06-21 10:37:18 -0500624 r"""
625 Send commands in ffdc_config file to targeted system.
626
627 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530628 ffdc_actions_for_target_type Commands and files for the selected
629 remote host type.
630 form_filename If true, pre-pend self.target_type to
631 filename
Peter D Phan04aca3b2021-06-21 10:37:18 -0500632 """
633
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500634 # Executing commands, if any
Patrick Williams20f38712022-12-08 06:18:26 -0600635 self.ssh_execute_ffdc_commands(
636 ffdc_actions_for_target_type, form_filename
637 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500638
Peter D Phan3beb02e2021-07-06 13:25:17 -0500639 # Copying files
Peter D Phan5963d632021-07-12 09:58:55 -0500640 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600641 self.logger.info(
642 "\n\n\tCopying FFDC files from remote system %s.\n"
643 % self.hostname
644 )
Peter D Phan2b8052d2021-06-22 10:55:41 -0500645
Peter D Phan04aca3b2021-06-21 10:37:18 -0500646 # Retrieving files from target system
George Keishingf5a57502021-07-22 16:43:47 -0500647 list_of_files = self.get_file_list(ffdc_actions_for_target_type)
Patrick Williams20f38712022-12-08 06:18:26 -0600648 self.scp_ffdc(
649 self.ffdc_dir_path,
650 self.ffdc_prefix,
651 form_filename,
652 list_of_files,
653 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500654 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600655 self.logger.info(
656 "\n\n\tSkip copying FFDC files from remote system %s.\n"
657 % self.hostname
658 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500659
Patrick Williams20f38712022-12-08 06:18:26 -0600660 def get_command_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500661 r"""
662 Fetch list of commands from configuration file
663
664 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530665 ffdc_actions_for_target_type Commands and files for the selected
666 remote host type.
Peter D Phanbabf2962021-07-07 11:24:40 -0500667 """
668 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600669 list_of_commands = ffdc_actions_for_target_type["COMMANDS"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500670 except KeyError:
671 list_of_commands = []
672 return list_of_commands
673
Patrick Williams20f38712022-12-08 06:18:26 -0600674 def get_file_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500675 r"""
676 Fetch list of commands from configuration file
677
678 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530679 ffdc_actions_for_target_type Commands and files for the selected
680 remote host type.
Peter D Phanbabf2962021-07-07 11:24:40 -0500681 """
682 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600683 list_of_files = ffdc_actions_for_target_type["FILES"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500684 except KeyError:
685 list_of_files = []
686 return list_of_files
687
Patrick Williams20f38712022-12-08 06:18:26 -0600688 def unpack_command(self, command):
Peter D Phan5963d632021-07-12 09:58:55 -0500689 r"""
690 Unpack command from config file
691
692 Description of argument(s):
693 command Command from config file.
694 """
695 if isinstance(command, dict):
696 command_txt = next(iter(command))
697 command_timeout = next(iter(command.values()))
698 elif isinstance(command, str):
699 command_txt = command
700 # Default command timeout 60 seconds
701 command_timeout = 60
702
703 return command_txt, command_timeout
704
Patrick Williams20f38712022-12-08 06:18:26 -0600705 def ssh_execute_ffdc_commands(
706 self, ffdc_actions_for_target_type, form_filename=False
707 ):
Peter D Phan3beb02e2021-07-06 13:25:17 -0500708 r"""
709 Send commands in ffdc_config file to targeted system.
710
711 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530712 ffdc_actions_for_target_type Commands and files for the selected
713 remote host type.
714 form_filename If true, pre-pend self.target_type to
715 filename
Peter D Phan3beb02e2021-07-06 13:25:17 -0500716 """
Patrick Williams20f38712022-12-08 06:18:26 -0600717 self.logger.info(
718 "\n\t[Run] Executing commands on %s using %s"
719 % (self.hostname, ffdc_actions_for_target_type["PROTOCOL"][0])
720 )
Peter D Phan3beb02e2021-07-06 13:25:17 -0500721
George Keishingf5a57502021-07-22 16:43:47 -0500722 list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500723 # If command list is empty, returns
724 if not list_of_commands:
725 return
726
727 progress_counter = 0
728 for command in list_of_commands:
Peter D Phan5963d632021-07-12 09:58:55 -0500729 command_txt, command_timeout = self.unpack_command(command)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500730
731 if form_filename:
732 command_txt = str(command_txt % self.target_type)
733
Patrick Williams20f38712022-12-08 06:18:26 -0600734 (
735 cmd_exit_code,
736 err,
737 response,
738 ) = self.ssh_remoteclient.execute_command(
739 command_txt, command_timeout
740 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500741
742 if cmd_exit_code:
743 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600744 "\n\t\t[WARN] %s exits with code %s."
745 % (command_txt, str(cmd_exit_code))
746 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500747 self.logger.warning("\t\t[WARN] %s " % err)
Peter D Phanbabf2962021-07-07 11:24:40 -0500748
Peter D Phan3beb02e2021-07-06 13:25:17 -0500749 progress_counter += 1
750 self.print_progress(progress_counter)
751
Peter D Phane86d9a52021-07-15 10:42:25 -0500752 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan3beb02e2021-07-06 13:25:17 -0500753
Patrick Williams20f38712022-12-08 06:18:26 -0600754 def group_copy(self, ffdc_actions_for_target_type):
Peter D Phan56429a62021-06-23 08:38:29 -0500755 r"""
756 scp group of files (wild card) from remote host.
757
758 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530759 fdc_actions_for_target_type Commands and files for the selected
760 remote host type.
Peter D Phan56429a62021-06-23 08:38:29 -0500761 """
Peter D Phan3beb02e2021-07-06 13:25:17 -0500762
Peter D Phan5963d632021-07-12 09:58:55 -0500763 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600764 self.logger.info(
765 "\n\tCopying files from remote system %s via SCP.\n"
766 % self.hostname
767 )
Peter D Phan56429a62021-06-23 08:38:29 -0500768
Patrick Williams20f38712022-12-08 06:18:26 -0600769 list_of_commands = self.get_command_list(
770 ffdc_actions_for_target_type
771 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500772 # If command list is empty, returns
773 if not list_of_commands:
774 return
Peter D Phan56429a62021-06-23 08:38:29 -0500775
Peter D Phanbabf2962021-07-07 11:24:40 -0500776 for command in list_of_commands:
777 try:
George Keishingb4540e72021-08-02 13:48:46 -0500778 command = self.yaml_env_and_plugin_vars_populate(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500779 except IndexError:
George Keishingb4540e72021-08-02 13:48:46 -0500780 self.logger.error("\t\tInvalid command %s" % command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500781 continue
782
Patrick Williams20f38712022-12-08 06:18:26 -0600783 (
784 cmd_exit_code,
785 err,
786 response,
787 ) = self.ssh_remoteclient.execute_command(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500788
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500789 # If file does not exist, code take no action.
790 # cmd_exit_code is ignored for this scenario.
Peter D Phan56429a62021-06-23 08:38:29 -0500791 if response:
Patrick Williams20f38712022-12-08 06:18:26 -0600792 scp_result = self.ssh_remoteclient.scp_file_from_remote(
793 response.split("\n"), self.ffdc_dir_path
794 )
Peter D Phan56429a62021-06-23 08:38:29 -0500795 if scp_result:
Patrick Williams20f38712022-12-08 06:18:26 -0600796 self.logger.info(
797 "\t\tSuccessfully copied from "
798 + self.hostname
799 + ":"
800 + command
801 )
Peter D Phan56429a62021-06-23 08:38:29 -0500802 else:
George Keishinga56e87b2021-08-06 00:24:19 -0500803 self.logger.info("\t\t%s has no result" % command)
Peter D Phan56429a62021-06-23 08:38:29 -0500804
805 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600806 self.logger.info(
807 "\n\n\tSkip copying files from remote system %s.\n"
808 % self.hostname
809 )
Peter D Phan56429a62021-06-23 08:38:29 -0500810
Patrick Williams20f38712022-12-08 06:18:26 -0600811 def scp_ffdc(
812 self,
813 targ_dir_path,
814 targ_file_prefix,
815 form_filename,
816 file_list=None,
817 quiet=None,
818 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500819 r"""
George Keishingc754b432025-04-24 14:27:14 +0530820 SCP all files in file_dict to the indicated directory on the local
821 system.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500822
823 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530824 targ_dir_path The path of the directory to receive
825 the files.
George Keishinge16f1582022-12-15 07:32:21 -0600826 targ_file_prefix Prefix which will be prepended to each
Peter D Phan72ce6b82021-06-03 06:18:26 -0500827 target file's name.
George Keishingc754b432025-04-24 14:27:14 +0530828 file_dict A dictionary of files to scp from
829 targeted system to this system
Peter D Phan72ce6b82021-06-03 06:18:26 -0500830
831 """
832
Peter D Phan72ce6b82021-06-03 06:18:26 -0500833 progress_counter = 0
834 for filename in file_list:
Peter D Phan2b8052d2021-06-22 10:55:41 -0500835 if form_filename:
836 filename = str(filename % self.target_type)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500837 source_file_path = filename
Patrick Williams20f38712022-12-08 06:18:26 -0600838 targ_file_path = (
839 targ_dir_path + targ_file_prefix + filename.split("/")[-1]
840 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500841
Peter D Phanbabf2962021-07-07 11:24:40 -0500842 # If source file name contains wild card, copy filename as is.
Patrick Williams20f38712022-12-08 06:18:26 -0600843 if "*" in source_file_path:
844 scp_result = self.ssh_remoteclient.scp_file_from_remote(
845 source_file_path, self.ffdc_dir_path
846 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500847 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600848 scp_result = self.ssh_remoteclient.scp_file_from_remote(
849 source_file_path, targ_file_path
850 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500851
852 if not quiet:
853 if scp_result:
Peter D Phane86d9a52021-07-15 10:42:25 -0500854 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -0600855 "\t\tSuccessfully copied from "
856 + self.hostname
857 + ":"
858 + source_file_path
859 + ".\n"
860 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500861 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500862 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -0600863 "\t\tFail to copy from "
864 + self.hostname
865 + ":"
866 + source_file_path
867 + ".\n"
868 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500869 else:
870 progress_counter += 1
871 self.print_progress(progress_counter)
872
Peter D Phan5e56f522021-12-20 13:19:41 -0600873 def set_ffdc_default_store_path(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500874 r"""
875 Set a default value for self.ffdc_dir_path and self.ffdc_prefix.
George Keishingc754b432025-04-24 14:27:14 +0530876 Collected ffdc file will be stored in dir
877 /self.location/hostname_timestr/.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500878 Individual ffdc file will have timestr_filename.
879
880 Description of class variables:
George Keishingc754b432025-04-24 14:27:14 +0530881 self.ffdc_dir_path The dir path where collected ffdc data files
882 should be put.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500883
884 self.ffdc_prefix The prefix to be given to each ffdc file name.
885
886 """
887
888 timestr = time.strftime("%Y%m%d-%H%M%S")
Patrick Williams20f38712022-12-08 06:18:26 -0600889 self.ffdc_dir_path = (
890 self.location + "/" + self.hostname + "_" + timestr + "/"
891 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500892 self.ffdc_prefix = timestr + "_"
893 self.validate_local_store(self.ffdc_dir_path)
894
Peter D Phan5e56f522021-12-20 13:19:41 -0600895 # Need to verify local store path exists prior to instantiate this class.
896 # This class method is used to share the same code between CLI input parm
897 # and Robot Framework "${EXECDIR}/logs" before referencing this class.
898 @classmethod
899 def validate_local_store(cls, dir_path):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500900 r"""
901 Ensure path exists to store FFDC files locally.
902
903 Description of variable:
904 dir_path The dir path where collected ffdc data files will be stored.
905
906 """
907
908 if not os.path.exists(dir_path):
909 try:
George Keishing7b3a5132021-07-13 09:24:02 -0500910 os.makedirs(dir_path, 0o755)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500911 except (IOError, OSError) as e:
912 # PermissionError
913 if e.errno == EPERM or e.errno == EACCES:
George Keishing15352052025-04-24 18:55:47 +0530914 print(
Patrick Williams20f38712022-12-08 06:18:26 -0600915 "\tERROR: os.makedirs %s failed with"
916 " PermissionError.\n" % dir_path
917 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500918 else:
George Keishing15352052025-04-24 18:55:47 +0530919 print(
Patrick Williams20f38712022-12-08 06:18:26 -0600920 "\tERROR: os.makedirs %s failed with %s.\n"
921 % (dir_path, e.strerror)
922 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500923 sys.exit(-1)
924
925 def print_progress(self, progress):
926 r"""
927 Print activity progress +
928
929 Description of variable:
930 progress Progress counter.
931
932 """
933
934 sys.stdout.write("\r\t" + "+" * progress)
935 sys.stdout.flush()
Patrick Williams20f38712022-12-08 06:18:26 -0600936 time.sleep(0.1)
Peter D Phan0c669772021-06-24 13:52:42 -0500937
938 def verify_redfish(self):
939 r"""
940 Verify remote host has redfish service active
941
942 """
Patrick Williams20f38712022-12-08 06:18:26 -0600943 redfish_parm = (
944 "redfishtool -r "
945 + self.hostname
George Keishing7a61aa22023-06-26 13:18:37 +0530946 + ":"
947 + self.port_https
Patrick Williams20f38712022-12-08 06:18:26 -0600948 + " -S Always raw GET /redfish/v1/"
949 )
950 return self.run_tool_cmd(redfish_parm, True)
Peter D Phan0c669772021-06-24 13:52:42 -0500951
George Keishingeafba182021-06-29 13:44:58 -0500952 def verify_ipmi(self):
953 r"""
954 Verify remote host has IPMI LAN service active
955
956 """
Patrick Williams20f38712022-12-08 06:18:26 -0600957 if self.target_type == "OPENBMC":
958 ipmi_parm = (
959 "ipmitool -I lanplus -C 17 -U "
960 + self.username
961 + " -P "
962 + self.password
963 + " -H "
964 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +0530965 + " -p "
966 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -0600967 + " power status"
968 )
George Keishing484f8242021-07-27 01:42:02 -0500969 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600970 ipmi_parm = (
971 "ipmitool -I lanplus -P "
972 + self.password
973 + " -H "
974 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +0530975 + " -p "
976 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -0600977 + " power status"
978 )
George Keishing484f8242021-07-27 01:42:02 -0500979
Patrick Williams20f38712022-12-08 06:18:26 -0600980 return self.run_tool_cmd(ipmi_parm, True)
George Keishingeafba182021-06-29 13:44:58 -0500981
Patrick Williams20f38712022-12-08 06:18:26 -0600982 def run_tool_cmd(self, parms_string, quiet=False):
George Keishingeafba182021-06-29 13:44:58 -0500983 r"""
George Keishing506b0582021-07-27 09:31:22 -0500984 Run CLI standard tool or scripts.
George Keishingeafba182021-06-29 13:44:58 -0500985
986 Description of variable:
George Keishing506b0582021-07-27 09:31:22 -0500987 parms_string tool command options.
988 quiet do not print tool error message if True
George Keishingeafba182021-06-29 13:44:58 -0500989 """
990
Patrick Williams20f38712022-12-08 06:18:26 -0600991 result = subprocess.run(
992 [parms_string],
993 stdout=subprocess.PIPE,
994 stderr=subprocess.PIPE,
995 shell=True,
996 universal_newlines=True,
997 )
George Keishingeafba182021-06-29 13:44:58 -0500998
999 if result.stderr and not quiet:
George Keishing0e9b5ba2025-05-08 12:17:58 +05301000 if self.password in parms_string:
1001 parms_string = parms_string.replace(self.password, "********")
Patrick Williams20f38712022-12-08 06:18:26 -06001002 self.logger.error("\n\t\tERROR with %s " % parms_string)
1003 self.logger.error("\t\t" + result.stderr)
George Keishingeafba182021-06-29 13:44:58 -05001004
1005 return result.stdout
George Keishing04d29102021-07-16 02:05:57 -05001006
George Keishingf5a57502021-07-22 16:43:47 -05001007 def verify_protocol(self, protocol_list):
1008 r"""
1009 Perform protocol working check.
1010
1011 Description of argument(s):
1012 protocol_list List of protocol.
1013 """
1014
1015 tmp_list = []
1016 if self.target_is_pingable():
1017 tmp_list.append("SHELL")
1018
1019 for protocol in protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -06001020 if self.remote_protocol != "ALL":
George Keishingf5a57502021-07-22 16:43:47 -05001021 if self.remote_protocol != protocol:
1022 continue
1023
1024 # Only check SSH/SCP once for both protocols
Patrick Williams20f38712022-12-08 06:18:26 -06001025 if (
1026 protocol == "SSH"
1027 or protocol == "SCP"
1028 and protocol not in tmp_list
1029 ):
George Keishingf5a57502021-07-22 16:43:47 -05001030 if self.ssh_to_target_system():
George Keishingaa638702021-07-26 11:48:28 -05001031 # Add only what user asked.
Patrick Williams20f38712022-12-08 06:18:26 -06001032 if self.remote_protocol != "ALL":
George Keishingaa638702021-07-26 11:48:28 -05001033 tmp_list.append(self.remote_protocol)
1034 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001035 tmp_list.append("SSH")
1036 tmp_list.append("SCP")
George Keishingf5a57502021-07-22 16:43:47 -05001037
Patrick Williams20f38712022-12-08 06:18:26 -06001038 if protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -05001039 if self.telnet_to_target_system():
1040 tmp_list.append(protocol)
1041
Patrick Williams20f38712022-12-08 06:18:26 -06001042 if protocol == "REDFISH":
George Keishingf5a57502021-07-22 16:43:47 -05001043 if self.verify_redfish():
1044 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001045 self.logger.info(
1046 "\n\t[Check] %s Redfish Service.\t\t [OK]"
1047 % self.hostname
1048 )
George Keishingf5a57502021-07-22 16:43:47 -05001049 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001050 self.logger.info(
1051 "\n\t[Check] %s Redfish Service.\t\t [NOT AVAILABLE]"
1052 % self.hostname
1053 )
George Keishingf5a57502021-07-22 16:43:47 -05001054
Patrick Williams20f38712022-12-08 06:18:26 -06001055 if protocol == "IPMI":
George Keishingf5a57502021-07-22 16:43:47 -05001056 if self.verify_ipmi():
1057 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001058 self.logger.info(
1059 "\n\t[Check] %s IPMI LAN Service.\t\t [OK]"
1060 % self.hostname
1061 )
George Keishingf5a57502021-07-22 16:43:47 -05001062 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001063 self.logger.info(
1064 "\n\t[Check] %s IPMI LAN Service.\t\t [NOT AVAILABLE]"
1065 % self.hostname
1066 )
George Keishingf5a57502021-07-22 16:43:47 -05001067
1068 return tmp_list
George Keishinge1686752021-07-27 12:55:28 -05001069
1070 def load_env(self):
1071 r"""
George Keishing0e9b5ba2025-05-08 12:17:58 +05301072 Load the user environment variables from a YAML file.
George Keishinge1686752021-07-27 12:55:28 -05001073
George Keishing0e9b5ba2025-05-08 12:17:58 +05301074 This method reads the environment variables from a YAML file specified
1075 in the ENV_FILE environment variable. If the file is not found or
1076 there is an error reading the file, an exception is raised.
1077
1078 The YAML file should have the following format:
1079
1080 .. code-block:: yaml
1081
1082 VAR_NAME: VAR_VALUE
1083
1084 Where VAR_NAME is the name of the environment variable, and
1085 VAR_VALUE is its value.
1086
1087 After loading the environment variables, they are stored in the
1088 self.env attribute for later use.
George Keishinge1686752021-07-27 12:55:28 -05001089 """
George Keishing0e9b5ba2025-05-08 12:17:58 +05301090
Patrick Williams20f38712022-12-08 06:18:26 -06001091 os.environ["hostname"] = self.hostname
1092 os.environ["username"] = self.username
1093 os.environ["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301094 os.environ["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301095 os.environ["port_https"] = self.port_https
1096 os.environ["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001097
1098 # Append default Env.
Patrick Williams20f38712022-12-08 06:18:26 -06001099 self.env_dict["hostname"] = self.hostname
1100 self.env_dict["username"] = self.username
1101 self.env_dict["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301102 self.env_dict["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301103 self.env_dict["port_https"] = self.port_https
1104 self.env_dict["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001105
1106 try:
1107 tmp_env_dict = {}
1108 if self.env_vars:
1109 tmp_env_dict = json.loads(self.env_vars)
1110 # Export ENV vars default.
1111 for key, value in tmp_env_dict.items():
1112 os.environ[key] = value
1113 self.env_dict[key] = str(value)
1114
George Keishing0e9b5ba2025-05-08 12:17:58 +05301115 # Load user specified ENV config YAML.
George Keishinge1686752021-07-27 12:55:28 -05001116 if self.econfig:
Patrick Williams20f38712022-12-08 06:18:26 -06001117 with open(self.econfig, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -05001118 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -07001119 tmp_env_dict = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -05001120 except yaml.YAMLError as e:
1121 self.logger.error(e)
1122 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001123 # Export ENV vars.
Patrick Williams20f38712022-12-08 06:18:26 -06001124 for key, value in tmp_env_dict["env_params"].items():
George Keishinge1686752021-07-27 12:55:28 -05001125 os.environ[key] = str(value)
1126 self.env_dict[key] = str(value)
1127 except json.decoder.JSONDecodeError as e:
1128 self.logger.error("\n\tERROR: %s " % e)
1129 sys.exit(-1)
George Keishing0e9b5ba2025-05-08 12:17:58 +05301130 except FileNotFoundError as e:
1131 self.logger.error("\n\tERROR: %s " % e)
1132 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001133
1134 # This to mask the password from displaying on the console.
1135 mask_dict = self.env_dict.copy()
1136 for k, v in mask_dict.items():
1137 if k.lower().find("password") != -1:
1138 hidden_text = []
1139 hidden_text.append(v)
Patrick Williams20f38712022-12-08 06:18:26 -06001140 password_regex = (
1141 "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1142 )
George Keishinge1686752021-07-27 12:55:28 -05001143 mask_dict[k] = re.sub(password_regex, "********", v)
1144
1145 self.logger.info(json.dumps(mask_dict, indent=8, sort_keys=False))
George Keishingb97a9042021-07-29 07:41:20 -05001146
1147 def execute_python_eval(self, eval_string):
1148 r"""
George Keishing9348b402021-08-13 12:22:35 -05001149 Execute qualified python function string using eval.
George Keishingb97a9042021-07-29 07:41:20 -05001150
1151 Description of argument(s):
1152 eval_string Execute the python object.
1153
1154 Example:
1155 eval(plugin.foo_func.foo_func(10))
1156 """
1157 try:
George Keishingdda48ce2021-08-12 07:02:27 -05001158 self.logger.info("\tExecuting plugin func()")
1159 self.logger.debug("\tCall func: %s" % eval_string)
George Keishingb97a9042021-07-29 07:41:20 -05001160 result = eval(eval_string)
1161 self.logger.info("\treturn: %s" % str(result))
Patrick Williams20f38712022-12-08 06:18:26 -06001162 except (
1163 ValueError,
1164 SyntaxError,
1165 NameError,
1166 AttributeError,
1167 TypeError,
1168 ) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001169 self.logger.error("\tERROR: execute_python_eval: %s" % e)
1170 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001171 plugin_error_dict["exit_on_error"] = True
George Keishing73b95d12021-08-13 14:30:52 -05001172 self.logger.info("\treturn: PLUGIN_EVAL_ERROR")
Patrick Williams20f38712022-12-08 06:18:26 -06001173 return "PLUGIN_EVAL_ERROR"
George Keishingb97a9042021-07-29 07:41:20 -05001174
1175 return result
1176
1177 def execute_plugin_block(self, plugin_cmd_list):
1178 r"""
Peter D Phan5e56f522021-12-20 13:19:41 -06001179 Pack the plugin command to qualifed python string object.
George Keishingb97a9042021-07-29 07:41:20 -05001180
1181 Description of argument(s):
1182 plugin_list_dict Plugin block read from YAML
1183 [{'plugin_name': 'plugin.foo_func.my_func'},
1184 {'plugin_args': [10]}]
1185
1186 Example:
1187 - plugin:
1188 - plugin_name: plugin.foo_func.my_func
1189 - plugin_args:
1190 - arg1
1191 - arg2
1192
1193 - plugin:
1194 - plugin_name: result = plugin.foo_func.my_func
1195 - plugin_args:
1196 - arg1
1197 - arg2
1198
1199 - plugin:
1200 - plugin_name: result1,result2 = plugin.foo_func.my_func
1201 - plugin_args:
1202 - arg1
1203 - arg2
1204 """
1205 try:
Patrick Williams20f38712022-12-08 06:18:26 -06001206 idx = self.key_index_list_dict("plugin_name", plugin_cmd_list)
1207 plugin_name = plugin_cmd_list[idx]["plugin_name"]
George Keishingb97a9042021-07-29 07:41:20 -05001208 # Equal separator means plugin function returns result.
Patrick Williams20f38712022-12-08 06:18:26 -06001209 if " = " in plugin_name:
George Keishingb97a9042021-07-29 07:41:20 -05001210 # Ex. ['result', 'plugin.foo_func.my_func']
Patrick Williams20f38712022-12-08 06:18:26 -06001211 plugin_name_args = plugin_name.split(" = ")
George Keishingb97a9042021-07-29 07:41:20 -05001212 # plugin func return data.
1213 for arg in plugin_name_args:
1214 if arg == plugin_name_args[-1]:
1215 plugin_name = arg
1216 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001217 plugin_resp = arg.split(",")
George Keishingb97a9042021-07-29 07:41:20 -05001218 # ['result1','result2']
1219 for x in plugin_resp:
1220 global_plugin_list.append(x)
1221 global_plugin_dict[x] = ""
1222
1223 # Walk the plugin args ['arg1,'arg2']
1224 # If the YAML plugin statement 'plugin_args' is not declared.
Patrick Williams20f38712022-12-08 06:18:26 -06001225 if any("plugin_args" in d for d in plugin_cmd_list):
1226 idx = self.key_index_list_dict("plugin_args", plugin_cmd_list)
1227 plugin_args = plugin_cmd_list[idx]["plugin_args"]
George Keishingb97a9042021-07-29 07:41:20 -05001228 if plugin_args:
1229 plugin_args = self.yaml_args_populate(plugin_args)
1230 else:
1231 plugin_args = []
1232 else:
1233 plugin_args = self.yaml_args_populate([])
1234
1235 # Pack the args arg1, arg2, .... argn into
1236 # "arg1","arg2","argn" string as params for function.
1237 parm_args_str = self.yaml_args_string(plugin_args)
1238 if parm_args_str:
Patrick Williams20f38712022-12-08 06:18:26 -06001239 plugin_func = plugin_name + "(" + parm_args_str + ")"
George Keishingb97a9042021-07-29 07:41:20 -05001240 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001241 plugin_func = plugin_name + "()"
George Keishingb97a9042021-07-29 07:41:20 -05001242
1243 # Execute plugin function.
1244 if global_plugin_dict:
1245 resp = self.execute_python_eval(plugin_func)
George Keishing9348b402021-08-13 12:22:35 -05001246 # Update plugin vars dict if there is any.
Patrick Williams20f38712022-12-08 06:18:26 -06001247 if resp != "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001248 self.response_args_data(resp)
George Keishingb97a9042021-07-29 07:41:20 -05001249 else:
George Keishingcaa97e62021-08-03 14:00:09 -05001250 resp = self.execute_python_eval(plugin_func)
George Keishingb97a9042021-07-29 07:41:20 -05001251 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001252 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001253 plugin_error_dict["exit_on_error"] = True
George Keishing1e7b0182021-08-06 14:05:54 -05001254 self.logger.error("\tERROR: execute_plugin_block: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001255 pass
1256
George Keishing73b95d12021-08-13 14:30:52 -05001257 # There is a real error executing the plugin function.
Patrick Williams20f38712022-12-08 06:18:26 -06001258 if resp == "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001259 return resp
1260
George Keishingde79a9b2021-08-12 16:14:43 -05001261 # Check if plugin_expects_return (int, string, list,dict etc)
Patrick Williams20f38712022-12-08 06:18:26 -06001262 if any("plugin_expects_return" in d for d in plugin_cmd_list):
1263 idx = self.key_index_list_dict(
1264 "plugin_expects_return", plugin_cmd_list
1265 )
1266 plugin_expects = plugin_cmd_list[idx]["plugin_expects_return"]
George Keishingde79a9b2021-08-12 16:14:43 -05001267 if plugin_expects:
1268 if resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001269 if (
1270 self.plugin_expect_type(plugin_expects, resp)
1271 == "INVALID"
1272 ):
George Keishingde79a9b2021-08-12 16:14:43 -05001273 self.logger.error("\tWARN: Plugin error check skipped")
1274 elif not self.plugin_expect_type(plugin_expects, resp):
Patrick Williams20f38712022-12-08 06:18:26 -06001275 self.logger.error(
1276 "\tERROR: Plugin expects return data: %s"
1277 % plugin_expects
1278 )
1279 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001280 elif not resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001281 self.logger.error(
1282 "\tERROR: Plugin func failed to return data"
1283 )
1284 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001285
1286 return resp
1287
George Keishingb97a9042021-07-29 07:41:20 -05001288 def response_args_data(self, plugin_resp):
1289 r"""
George Keishing9348b402021-08-13 12:22:35 -05001290 Parse the plugin function response and update plugin return variable.
George Keishingb97a9042021-07-29 07:41:20 -05001291
1292 plugin_resp Response data from plugin function.
1293 """
1294 resp_list = []
George Keishing5765f792021-08-02 13:08:53 -05001295 resp_data = ""
George Keishing9348b402021-08-13 12:22:35 -05001296
George Keishingb97a9042021-07-29 07:41:20 -05001297 # There is nothing to update the plugin response.
Patrick Williams20f38712022-12-08 06:18:26 -06001298 if len(global_plugin_list) == 0 or plugin_resp == "None":
George Keishingb97a9042021-07-29 07:41:20 -05001299 return
1300
George Keishing5765f792021-08-02 13:08:53 -05001301 if isinstance(plugin_resp, str):
Patrick Williams20f38712022-12-08 06:18:26 -06001302 resp_data = plugin_resp.strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001303 resp_list.append(resp_data)
1304 elif isinstance(plugin_resp, bytes):
Patrick Williams20f38712022-12-08 06:18:26 -06001305 resp_data = str(plugin_resp, "UTF-8").strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001306 resp_list.append(resp_data)
1307 elif isinstance(plugin_resp, tuple):
1308 if len(global_plugin_list) == 1:
George Keishingb97a9042021-07-29 07:41:20 -05001309 resp_list.append(plugin_resp)
George Keishing5765f792021-08-02 13:08:53 -05001310 else:
1311 resp_list = list(plugin_resp)
Patrick Williams20f38712022-12-08 06:18:26 -06001312 resp_list = [x.strip("\r\n\t") for x in resp_list]
George Keishingb97a9042021-07-29 07:41:20 -05001313 elif isinstance(plugin_resp, list):
George Keishing5765f792021-08-02 13:08:53 -05001314 if len(global_plugin_list) == 1:
Patrick Williams20f38712022-12-08 06:18:26 -06001315 resp_list.append([x.strip("\r\n\t") for x in plugin_resp])
George Keishing5765f792021-08-02 13:08:53 -05001316 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001317 resp_list = [x.strip("\r\n\t") for x in plugin_resp]
George Keishing5765f792021-08-02 13:08:53 -05001318 elif isinstance(plugin_resp, int) or isinstance(plugin_resp, float):
1319 resp_list.append(plugin_resp)
George Keishingb97a9042021-07-29 07:41:20 -05001320
George Keishing9348b402021-08-13 12:22:35 -05001321 # Iterate if there is a list of plugin return vars to update.
George Keishingb97a9042021-07-29 07:41:20 -05001322 for idx, item in enumerate(resp_list, start=0):
George Keishing9348b402021-08-13 12:22:35 -05001323 # Exit loop, done required loop.
George Keishingb97a9042021-07-29 07:41:20 -05001324 if idx >= len(global_plugin_list):
1325 break
1326 # Find the index of the return func in the list and
1327 # update the global func return dictionary.
1328 try:
1329 dict_idx = global_plugin_list[idx]
1330 global_plugin_dict[dict_idx] = item
1331 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001332 self.logger.warn("\tWARN: response_args_data: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001333 pass
1334
1335 # Done updating plugin dict irrespective of pass or failed,
George Keishing9348b402021-08-13 12:22:35 -05001336 # clear all the list element for next plugin block execute.
George Keishingb97a9042021-07-29 07:41:20 -05001337 global_plugin_list.clear()
1338
1339 def yaml_args_string(self, plugin_args):
1340 r"""
1341 Pack the args into string.
1342
1343 plugin_args arg list ['arg1','arg2,'argn']
1344 """
Patrick Williams20f38712022-12-08 06:18:26 -06001345 args_str = ""
George Keishingb97a9042021-07-29 07:41:20 -05001346 for args in plugin_args:
1347 if args:
George Keishing0581cb02021-08-05 15:08:58 -05001348 if isinstance(args, (int, float)):
George Keishingb97a9042021-07-29 07:41:20 -05001349 args_str += str(args)
George Keishing0581cb02021-08-05 15:08:58 -05001350 elif args in global_plugin_type_list:
1351 args_str += str(global_plugin_dict[args])
George Keishingb97a9042021-07-29 07:41:20 -05001352 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001353 args_str += '"' + str(args.strip("\r\n\t")) + '"'
George Keishingb97a9042021-07-29 07:41:20 -05001354 # Skip last list element.
1355 if args != plugin_args[-1]:
1356 args_str += ","
1357 return args_str
1358
1359 def yaml_args_populate(self, yaml_arg_list):
1360 r"""
George Keishing9348b402021-08-13 12:22:35 -05001361 Decode env and plugin vars and populate.
George Keishingb97a9042021-07-29 07:41:20 -05001362
1363 Description of argument(s):
1364 yaml_arg_list arg list read from YAML
1365
1366 Example:
1367 - plugin_args:
1368 - arg1
1369 - arg2
1370
1371 yaml_arg_list: [arg2, arg2]
1372 """
1373 # Get the env loaded keys as list ['hostname', 'username', 'password'].
George Keishingb97a9042021-07-29 07:41:20 -05001374
1375 if isinstance(yaml_arg_list, list):
1376 tmp_list = []
1377 for arg in yaml_arg_list:
George Keishing0581cb02021-08-05 15:08:58 -05001378 if isinstance(arg, (int, float)):
George Keishingb97a9042021-07-29 07:41:20 -05001379 tmp_list.append(arg)
1380 continue
1381 elif isinstance(arg, str):
1382 arg_str = self.yaml_env_and_plugin_vars_populate(str(arg))
1383 tmp_list.append(arg_str)
1384 else:
1385 tmp_list.append(arg)
1386
1387 # return populated list.
1388 return tmp_list
1389
1390 def yaml_env_and_plugin_vars_populate(self, yaml_arg_str):
1391 r"""
George Keishinga593f4b2025-05-13 20:02:36 +05301392 Update environment variables and plugin variables based on the
1393 provided YAML argument string.
George Keishingb97a9042021-07-29 07:41:20 -05001394
George Keishinga593f4b2025-05-13 20:02:36 +05301395 This method processes the yaml_arg_str argument, which is expected
1396 to contain a string representing environment variables and plugin
1397 variables in the format:
George Keishingb97a9042021-07-29 07:41:20 -05001398
George Keishinga593f4b2025-05-13 20:02:36 +05301399 .. code-block:: yaml
1400
George Keishingb97a9042021-07-29 07:41:20 -05001401 - cat ${MY_VAR}
1402 - ls -AX my_plugin_var
George Keishinga593f4b2025-05-13 20:02:36 +05301403
1404 The method parses the string, extracts the variable names, and updates
1405 the corresponding environment variables and plugin variables.
1406
1407 Parameters:
1408 yaml_arg_str (str): A string containing environment and plugin
1409 variable definitions in YAML format.
1410
1411 Returns:
1412 str: The updated YAML argument string with plugin variables
1413 replaced.
George Keishingb97a9042021-07-29 07:41:20 -05001414 """
George Keishinga593f4b2025-05-13 20:02:36 +05301415
1416 # Parse and convert the Plugin YAML vars string to python vars
1417 # Example:
1418 # ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
George Keishingb97a9042021-07-29 07:41:20 -05001419 try:
George Keishingc754b432025-04-24 14:27:14 +05301420 # Example, list of matching
1421 # env vars ['username', 'password', 'hostname']
George Keishingb97a9042021-07-29 07:41:20 -05001422 # Extra escape \ for special symbols. '\$\{([^\}]+)\}' works good.
George Keishinga593f4b2025-05-13 20:02:36 +05301423 env_var_regex = r"\$\{([^\}]+)\}"
1424 env_var_names_list = re.findall(env_var_regex, yaml_arg_str)
1425
George Keishingb97a9042021-07-29 07:41:20 -05001426 for var in env_var_names_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301427 env_var = os.environ.get(var)
1428 if env_var:
1429 env_replace = "${" + var + "}"
1430 yaml_arg_str = yaml_arg_str.replace(env_replace, env_var)
George Keishingb97a9042021-07-29 07:41:20 -05001431 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001432 self.logger.error("\tERROR:yaml_env_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001433 pass
1434
George Keishinga593f4b2025-05-13 20:02:36 +05301435 """
1436 Parse the string for plugin vars.
1437 Implement the logic to update environment variables based on the
1438 extracted variable names.
1439 """
George Keishingb97a9042021-07-29 07:41:20 -05001440 try:
George Keishinga593f4b2025-05-13 20:02:36 +05301441 # Example, list of plugin vars env_var_names_list
1442 # ['my_hostname', 'port_https']
1443 global_plugin_dict_keys = set(global_plugin_dict.keys())
1444 # Skip env var list already populated above code block list.
1445 plugin_var_name_list = [
1446 var
1447 for var in global_plugin_dict_keys
1448 if var not in env_var_names_list
1449 ]
1450
George Keishingb97a9042021-07-29 07:41:20 -05001451 for var in plugin_var_name_list:
George Keishinga593f4b2025-05-13 20:02:36 +05301452 plugin_var_value = global_plugin_dict[var]
George Keishing0581cb02021-08-05 15:08:58 -05001453 if yaml_arg_str in global_plugin_dict:
George Keishinga593f4b2025-05-13 20:02:36 +05301454 """
1455 If this plugin var exist but empty in dict, don't replace.
1456 his is either a YAML plugin statement incorrectly used or
1457 user added a plugin var which is not going to be populated.
1458 """
1459 if isinstance(plugin_var_value, (list, dict)):
1460 """
1461 List data type or dict can't be replaced, use
1462 directly in eval function call.
1463 """
George Keishing0581cb02021-08-05 15:08:58 -05001464 global_plugin_type_list.append(var)
1465 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001466 yaml_arg_str = yaml_arg_str.replace(
George Keishinga593f4b2025-05-13 20:02:36 +05301467 str(var), str(plugin_var_value)
Patrick Williams20f38712022-12-08 06:18:26 -06001468 )
George Keishingb97a9042021-07-29 07:41:20 -05001469 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001470 self.logger.error("\tERROR: yaml_plugin_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001471 pass
1472
George Keishinga593f4b2025-05-13 20:02:36 +05301473 # From ${my_hostname}:${port_https} -> ['my_hostname', 'port_https']
1474 # to populated values string as
1475 # Example: xx.xx.xx.xx:443 and return the string
George Keishingb97a9042021-07-29 07:41:20 -05001476 return yaml_arg_str
George Keishing1e7b0182021-08-06 14:05:54 -05001477
1478 def plugin_error_check(self, plugin_dict):
1479 r"""
George Keishing1e877422025-05-09 20:45:09 +05301480 Process plugin error dictionary and return the corresponding error
1481 message.
George Keishing1e7b0182021-08-06 14:05:54 -05001482
George Keishing1e877422025-05-09 20:45:09 +05301483 This method checks if any dictionary in the plugin_dict list contains
1484 a "plugin_error" key. If such a dictionary is found, it retrieves the
1485 value associated with the "plugin_error" key and returns the
1486 corresponding error message from the plugin_error_dict attribute.
1487
1488 Parameters:
1489 plugin_dict (list of dict): A list of dictionaries containing
1490 plugin error information.
1491
1492 Returns:
1493 str: The error message corresponding to the "plugin_error" value,
1494 or None if no error is found.
George Keishing1e7b0182021-08-06 14:05:54 -05001495 """
Patrick Williams20f38712022-12-08 06:18:26 -06001496 if any("plugin_error" in d for d in plugin_dict):
George Keishing1e7b0182021-08-06 14:05:54 -05001497 for d in plugin_dict:
Patrick Williams20f38712022-12-08 06:18:26 -06001498 if "plugin_error" in d:
1499 value = d["plugin_error"]
George Keishing1e877422025-05-09 20:45:09 +05301500 return self.plugin_error_dict.get(value, None)
1501 return None
George Keishingde79a9b2021-08-12 16:14:43 -05001502
1503 def key_index_list_dict(self, key, list_dict):
1504 r"""
George Keishing1e877422025-05-09 20:45:09 +05301505 Find the index of the first dictionary in the list that contains
1506 the specified key.
George Keishingde79a9b2021-08-12 16:14:43 -05001507
George Keishing1e877422025-05-09 20:45:09 +05301508 Parameters:
1509 key (str): The key to search for in the
1510 dictionaries.
1511 list_dict (list of dict): A list of dictionaries to search
1512 through.
1513
1514 Returns:
1515 int: The index of the first dictionary containing the key, or -1
1516 if no match is found.
George Keishingde79a9b2021-08-12 16:14:43 -05001517 """
1518 for i, d in enumerate(list_dict):
George Keishing1e877422025-05-09 20:45:09 +05301519 if key in d:
George Keishingde79a9b2021-08-12 16:14:43 -05001520 return i
George Keishing1e877422025-05-09 20:45:09 +05301521 return -1
George Keishingde79a9b2021-08-12 16:14:43 -05001522
1523 def plugin_expect_type(self, type, data):
1524 r"""
George Keishing1e877422025-05-09 20:45:09 +05301525 Check if the provided data matches the expected type.
1526
1527 This method checks if the data argument matches the specified type.
1528 It supports the following types: "int", "float", "str", "list", "dict",
1529 and "tuple".
1530
1531 If the type is not recognized, it logs an info message and returns
1532 "INVALID".
1533
1534 Parameters:
1535 type (str): The expected data type.
1536 data: The data to check against the expected type.
1537
1538 Returns:
1539 bool or str: True if the data matches the expected type, False if
1540 not, or "INVALID" if the type is not recognized.
George Keishingde79a9b2021-08-12 16:14:43 -05001541 """
Patrick Williams20f38712022-12-08 06:18:26 -06001542 if type == "int":
George Keishingde79a9b2021-08-12 16:14:43 -05001543 return isinstance(data, int)
Patrick Williams20f38712022-12-08 06:18:26 -06001544 elif type == "float":
George Keishingde79a9b2021-08-12 16:14:43 -05001545 return isinstance(data, float)
Patrick Williams20f38712022-12-08 06:18:26 -06001546 elif type == "str":
George Keishingde79a9b2021-08-12 16:14:43 -05001547 return isinstance(data, str)
Patrick Williams20f38712022-12-08 06:18:26 -06001548 elif type == "list":
George Keishingde79a9b2021-08-12 16:14:43 -05001549 return isinstance(data, list)
Patrick Williams20f38712022-12-08 06:18:26 -06001550 elif type == "dict":
George Keishingde79a9b2021-08-12 16:14:43 -05001551 return isinstance(data, dict)
Patrick Williams20f38712022-12-08 06:18:26 -06001552 elif type == "tuple":
George Keishingde79a9b2021-08-12 16:14:43 -05001553 return isinstance(data, tuple)
1554 else:
1555 self.logger.info("\tInvalid data type requested: %s" % type)
Patrick Williams20f38712022-12-08 06:18:26 -06001556 return "INVALID"