blob: 03c2d558853ea885a6a50244d9e49b243d9b098f [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
Peter D Phancb791d72022-02-08 12:23:03 -060019script_dir = os.path.dirname(os.path.abspath(__file__))
20sys.path.append(script_dir)
21# Walk path and append to sys.path
22for root, dirs, files in os.walk(script_dir):
23 for dir in dirs:
24 sys.path.append(os.path.join(root, dir))
25
Patrick Williams20f38712022-12-08 06:18:26 -060026from ssh_utility import SSHRemoteclient # NOQA
27from telnet_utility import TelnetRemoteclient # NOQA
Peter D Phan72ce6b82021-06-03 06:18:26 -050028
George Keishingb97a9042021-07-29 07:41:20 -050029r"""
30User define plugins python functions.
31
32It will imports files from directory plugins
33
34plugins
35├── file1.py
36└── file2.py
37
38Example how to define in YAML:
39 - plugin:
40 - plugin_name: plugin.foo_func.foo_func_yaml
41 - plugin_args:
42 - arg1
43 - arg2
44"""
George Keishing0e9b5ba2025-05-08 12:17:58 +053045plugin_dir = os.path.join(os.path.dirname(__file__), "plugins")
Peter D Phan5e56f522021-12-20 13:19:41 -060046sys.path.append(plugin_dir)
George Keishing0e9b5ba2025-05-08 12:17:58 +053047
48for module in os.listdir(plugin_dir):
49 if module == "__init__.py" or not module.endswith(".py"):
50 continue
51
52 plugin_module = f"plugins.{module[:-3]}"
53 try:
54 plugin = __import__(plugin_module, globals(), locals(), [], 0)
55 except Exception as e:
56 print(f"PLUGIN: Exception: {e}")
57 print(f"PLUGIN: Module import failed: {module}")
58 continue
George Keishingb97a9042021-07-29 07:41:20 -050059
60r"""
61This is for plugin functions returning data or responses to the caller
62in YAML plugin setup.
63
64Example:
65
66 - plugin:
67 - plugin_name: version = plugin.ssh_execution.ssh_execute_cmd
68 - plugin_args:
69 - ${hostname}
70 - ${username}
71 - ${password}
72 - "cat /etc/os-release | grep VERSION_ID | awk -F'=' '{print $2}'"
73 - plugin:
74 - plugin_name: plugin.print_vars.print_vars
75 - plugin_args:
76 - version
77
78where first plugin "version" var is used by another plugin in the YAML
79block or plugin
80
81"""
82global global_log_store_path
83global global_plugin_dict
84global global_plugin_list
George Keishing9348b402021-08-13 12:22:35 -050085
George Keishing0581cb02021-08-05 15:08:58 -050086# Hold the plugin return values in dict and plugin return vars in list.
George Keishing9348b402021-08-13 12:22:35 -050087# Dict is to reference and update vars processing in parser where as
88# list is for current vars from the plugin block which needs processing.
George Keishingb97a9042021-07-29 07:41:20 -050089global_plugin_dict = {}
90global_plugin_list = []
George Keishing9348b402021-08-13 12:22:35 -050091
George Keishingc754b432025-04-24 14:27:14 +053092# Hold the plugin return named declared if function returned values are
93# list,dict.
George Keishing0581cb02021-08-05 15:08:58 -050094# Refer this name list to look up the plugin dict for eval() args function
George Keishing9348b402021-08-13 12:22:35 -050095# Example ['version']
George Keishing0581cb02021-08-05 15:08:58 -050096global_plugin_type_list = []
George Keishing9348b402021-08-13 12:22:35 -050097
98# Path where logs are to be stored or written.
Patrick Williams20f38712022-12-08 06:18:26 -060099global_log_store_path = ""
George Keishingb97a9042021-07-29 07:41:20 -0500100
George Keishing1e7b0182021-08-06 14:05:54 -0500101# Plugin error state defaults.
102plugin_error_dict = {
Patrick Williams20f38712022-12-08 06:18:26 -0600103 "exit_on_error": False,
104 "continue_on_error": False,
George Keishing1e7b0182021-08-06 14:05:54 -0500105}
106
Peter D Phan72ce6b82021-06-03 06:18:26 -0500107
Peter D Phan5e56f522021-12-20 13:19:41 -0600108class ffdc_collector:
Peter D Phan72ce6b82021-06-03 06:18:26 -0500109 r"""
George Keishing1e7b0182021-08-06 14:05:54 -0500110 Execute commands from configuration file to collect log files.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500111 Fetch and store generated files at the specified location.
112
113 """
114
Patrick Williams20f38712022-12-08 06:18:26 -0600115 def __init__(
116 self,
117 hostname,
118 username,
119 password,
George Keishing7a61aa22023-06-26 13:18:37 +0530120 port_ssh,
George Keishinge8a41752023-06-22 21:42:47 +0530121 port_https,
122 port_ipmi,
Patrick Williams20f38712022-12-08 06:18:26 -0600123 ffdc_config,
124 location,
125 remote_type,
126 remote_protocol,
127 env_vars,
128 econfig,
129 log_level,
130 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500131 r"""
132 Description of argument(s):
133
George Keishingc754b432025-04-24 14:27:14 +0530134 hostname Name/ip of the targeted (remote) system
135 username User on the targeted system with access to
136 FFDC files
137 password Password for user on targeted system
George Keishing7a61aa22023-06-26 13:18:37 +0530138 port_ssh SSH port value. By default 22
George Keishinge8a41752023-06-22 21:42:47 +0530139 port_https HTTPS port value. By default 443
140 port_ipmi IPMI port value. By default 623
George Keishingc754b432025-04-24 14:27:14 +0530141 ffdc_config Configuration file listing commands and files
142 for FFDC
143 location Where to store collected FFDC
144 remote_type OS type of the remote host
George Keishing8e94f8c2021-07-23 15:06:32 -0500145 remote_protocol Protocol to use to collect data
146 env_vars User define CLI env vars '{"key : "value"}'
147 econfig User define env vars YAML file
Peter D Phan72ce6b82021-06-03 06:18:26 -0500148
149 """
Peter D Phane86d9a52021-07-15 10:42:25 -0500150
151 self.hostname = hostname
152 self.username = username
153 self.password = password
George Keishing7a61aa22023-06-26 13:18:37 +0530154 self.port_ssh = str(port_ssh)
George Keishinge8a41752023-06-22 21:42:47 +0530155 self.port_https = str(port_https)
156 self.port_ipmi = str(port_ipmi)
Peter D Phane86d9a52021-07-15 10:42:25 -0500157 self.ffdc_config = ffdc_config
158 self.location = location + "/" + remote_type.upper()
159 self.ssh_remoteclient = None
160 self.telnet_remoteclient = None
161 self.ffdc_dir_path = ""
162 self.ffdc_prefix = ""
163 self.target_type = remote_type.upper()
164 self.remote_protocol = remote_protocol.upper()
George Keishinge1686752021-07-27 12:55:28 -0500165 self.env_vars = env_vars
166 self.econfig = econfig
Peter D Phane86d9a52021-07-15 10:42:25 -0500167 self.start_time = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600168 self.elapsed_time = ""
Peter D Phane86d9a52021-07-15 10:42:25 -0500169 self.logger = None
170
171 # Set prefix values for scp files and directory.
George Keishingc754b432025-04-24 14:27:14 +0530172 # Since the time stamp is at second granularity, these values are set
173 # here to be sure that all files for this run will have same timestamps
Peter D Phane86d9a52021-07-15 10:42:25 -0500174 # and they will be saved in the same directory.
175 # self.location == local system for now
Peter D Phan5e56f522021-12-20 13:19:41 -0600176 self.set_ffdc_default_store_path()
Peter D Phane86d9a52021-07-15 10:42:25 -0500177
Peter D Phan5e56f522021-12-20 13:19:41 -0600178 # Logger for this run. Need to be after set_ffdc_default_store_path()
Peter D Phane86d9a52021-07-15 10:42:25 -0500179 self.script_logging(getattr(logging, log_level.upper()))
180
181 # Verify top level directory exists for storage
182 self.validate_local_store(self.location)
183
Peter D Phan72ce6b82021-06-03 06:18:26 -0500184 if self.verify_script_env():
Peter D Phane86d9a52021-07-15 10:42:25 -0500185 # Load default or user define YAML configuration file.
Patrick Williams20f38712022-12-08 06:18:26 -0600186 with open(self.ffdc_config, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -0500187 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -0700188 self.ffdc_actions = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -0500189 except yaml.YAMLError as e:
190 self.logger.error(e)
191 sys.exit(-1)
Peter D Phane86d9a52021-07-15 10:42:25 -0500192
193 if self.target_type not in self.ffdc_actions.keys():
194 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600195 "\n\tERROR: %s is not listed in %s.\n\n"
196 % (self.target_type, self.ffdc_config)
197 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500198 sys.exit(-1)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500199 else:
Peter D Phan8462faf2021-06-16 12:24:15 -0500200 sys.exit(-1)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500201
George Keishing4885b2f2021-07-21 15:22:45 -0500202 # Load ENV vars from user.
George Keishingaa1f8482021-07-22 00:54:55 -0500203 self.logger.info("\n\tENV: User define input YAML variables")
204 self.env_dict = {}
Peter D Phan5e56f522021-12-20 13:19:41 -0600205 self.load_env()
George Keishingaa1f8482021-07-22 00:54:55 -0500206
Peter D Phan72ce6b82021-06-03 06:18:26 -0500207 def verify_script_env(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500208 # Import to log version
209 import click
210 import paramiko
211
212 run_env_ok = True
Peter D Phan0c669772021-06-24 13:52:42 -0500213
George Keishingd805bc02025-02-28 12:17:13 +0530214 try:
215 redfishtool_version = (
216 self.run_tool_cmd("redfishtool -V").split(" ")[2].strip("\n")
217 )
218 except Exception as e:
219 self.logger.error("\tEXCEPTION redfishtool: %s", e)
220 redfishtool_version = "Not Installed (optional)"
221
222 try:
223 ipmitool_version = self.run_tool_cmd("ipmitool -V").split(" ")[2]
224 except Exception as e:
225 self.logger.error("\tEXCEPTION ipmitool: %s", e)
226 ipmitool_version = "Not Installed (optional)"
Peter D Phan0c669772021-06-24 13:52:42 -0500227
Peter D Phane86d9a52021-07-15 10:42:25 -0500228 self.logger.info("\n\t---- Script host environment ----")
Patrick Williams20f38712022-12-08 06:18:26 -0600229 self.logger.info(
230 "\t{:<10} {:<10}".format("Script hostname", os.uname()[1])
231 )
232 self.logger.info(
233 "\t{:<10} {:<10}".format("Script host os", platform.platform())
234 )
235 self.logger.info(
236 "\t{:<10} {:>10}".format("Python", platform.python_version())
237 )
238 self.logger.info("\t{:<10} {:>10}".format("PyYAML", yaml.__version__))
239 self.logger.info("\t{:<10} {:>10}".format("click", click.__version__))
240 self.logger.info(
241 "\t{:<10} {:>10}".format("paramiko", paramiko.__version__)
242 )
243 self.logger.info(
244 "\t{:<10} {:>9}".format("redfishtool", redfishtool_version)
245 )
246 self.logger.info(
247 "\t{:<10} {:>12}".format("ipmitool", ipmitool_version)
248 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500249
Patrick Williams20f38712022-12-08 06:18:26 -0600250 if eval(yaml.__version__.replace(".", ",")) < (5, 3, 0):
251 self.logger.error(
252 "\n\tERROR: Python or python packages do not meet minimum"
253 " version requirement."
254 )
255 self.logger.error(
256 "\tERROR: PyYAML version 5.3.0 or higher is needed.\n"
257 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500258 run_env_ok = False
259
Peter D Phane86d9a52021-07-15 10:42:25 -0500260 self.logger.info("\t---- End script host environment ----")
Peter D Phan72ce6b82021-06-03 06:18:26 -0500261 return run_env_ok
262
Patrick Williams20f38712022-12-08 06:18:26 -0600263 def script_logging(self, log_level_attr):
Peter D Phane86d9a52021-07-15 10:42:25 -0500264 r"""
265 Create logger
266
267 """
268 self.logger = logging.getLogger()
269 self.logger.setLevel(log_level_attr)
Patrick Williams20f38712022-12-08 06:18:26 -0600270 log_file_handler = logging.FileHandler(
271 self.ffdc_dir_path + "collector.log"
272 )
Peter D Phane86d9a52021-07-15 10:42:25 -0500273
274 stdout_handler = logging.StreamHandler(sys.stdout)
275 self.logger.addHandler(log_file_handler)
276 self.logger.addHandler(stdout_handler)
277
278 # Turn off paramiko INFO logging
279 logging.getLogger("paramiko").setLevel(logging.WARNING)
280
Peter D Phan72ce6b82021-06-03 06:18:26 -0500281 def target_is_pingable(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500282 r"""
283 Check if target system is ping-able.
284
285 """
George Keishing0662e942021-07-13 05:12:20 -0500286 response = os.system("ping -c 1 %s 2>&1 >/dev/null" % self.hostname)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500287 if response == 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600288 self.logger.info(
289 "\n\t[Check] %s is ping-able.\t\t [OK]" % self.hostname
290 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500291 return True
292 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500293 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600294 "\n\tERROR: %s is not ping-able. FFDC collection aborted.\n"
295 % self.hostname
296 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500297 sys.exit(-1)
298
Peter D Phan72ce6b82021-06-03 06:18:26 -0500299 def collect_ffdc(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500300 r"""
301 Initiate FFDC Collection depending on requested protocol.
302
303 """
304
Patrick Williams20f38712022-12-08 06:18:26 -0600305 self.logger.info(
306 "\n\t---- Start communicating with %s ----" % self.hostname
307 )
Peter D Phan7610bc42021-07-06 06:31:05 -0500308 self.start_time = time.time()
Peter D Phan0c669772021-06-24 13:52:42 -0500309
George Keishingf5a57502021-07-22 16:43:47 -0500310 # Find the list of target and protocol supported.
311 check_protocol_list = []
312 config_dict = self.ffdc_actions
Peter D Phan0c669772021-06-24 13:52:42 -0500313
George Keishingf5a57502021-07-22 16:43:47 -0500314 for target_type in config_dict.keys():
315 if self.target_type != target_type:
316 continue
George Keishingeafba182021-06-29 13:44:58 -0500317
George Keishingf5a57502021-07-22 16:43:47 -0500318 for k, v in config_dict[target_type].items():
Patrick Williams20f38712022-12-08 06:18:26 -0600319 if (
320 config_dict[target_type][k]["PROTOCOL"][0]
321 not in check_protocol_list
322 ):
323 check_protocol_list.append(
324 config_dict[target_type][k]["PROTOCOL"][0]
325 )
Peter D Phanbff617a2021-07-22 08:41:35 -0500326
Patrick Williams20f38712022-12-08 06:18:26 -0600327 self.logger.info(
328 "\n\t %s protocol type: %s"
329 % (self.target_type, check_protocol_list)
330 )
Peter D Phanbff617a2021-07-22 08:41:35 -0500331
George Keishingf5a57502021-07-22 16:43:47 -0500332 verified_working_protocol = self.verify_protocol(check_protocol_list)
Peter D Phanbff617a2021-07-22 08:41:35 -0500333
George Keishingf5a57502021-07-22 16:43:47 -0500334 if verified_working_protocol:
Patrick Williams20f38712022-12-08 06:18:26 -0600335 self.logger.info(
336 "\n\t---- Completed protocol pre-requisite check ----\n"
337 )
Peter D Phan0c669772021-06-24 13:52:42 -0500338
George Keishingf5a57502021-07-22 16:43:47 -0500339 # Verify top level directory exists for storage
340 self.validate_local_store(self.location)
341
Patrick Williams20f38712022-12-08 06:18:26 -0600342 if (self.remote_protocol not in verified_working_protocol) and (
343 self.remote_protocol != "ALL"
344 ):
345 self.logger.info(
346 "\n\tWorking protocol list: %s" % verified_working_protocol
347 )
George Keishingf5a57502021-07-22 16:43:47 -0500348 self.logger.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600349 "\tERROR: Requested protocol %s is not in working protocol"
George Keishing7899a452023-02-15 02:46:54 -0600350 " list.\n" % self.remote_protocol
Patrick Williams20f38712022-12-08 06:18:26 -0600351 )
George Keishingf5a57502021-07-22 16:43:47 -0500352 sys.exit(-1)
353 else:
354 self.generate_ffdc(verified_working_protocol)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500355
356 def ssh_to_target_system(self):
357 r"""
358 Open a ssh connection to targeted system.
359
360 """
361
Patrick Williams20f38712022-12-08 06:18:26 -0600362 self.ssh_remoteclient = SSHRemoteclient(
George Keishing7a61aa22023-06-26 13:18:37 +0530363 self.hostname, self.username, self.password, self.port_ssh
Patrick Williams20f38712022-12-08 06:18:26 -0600364 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500365
Peter D Phan5963d632021-07-12 09:58:55 -0500366 if self.ssh_remoteclient.ssh_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600367 self.logger.info(
368 "\n\t[Check] %s SSH connection established.\t [OK]"
369 % self.hostname
370 )
Peter D Phan733df632021-06-17 13:13:36 -0500371
Peter D Phan5963d632021-07-12 09:58:55 -0500372 # Check scp connection.
373 # If scp connection fails,
374 # continue with FFDC generation but skip scp files to local host.
375 self.ssh_remoteclient.scp_connection()
376 return True
377 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600378 self.logger.info(
379 "\n\t[Check] %s SSH connection.\t [NOT AVAILABLE]"
380 % self.hostname
381 )
Peter D Phan5963d632021-07-12 09:58:55 -0500382 return False
383
384 def telnet_to_target_system(self):
385 r"""
386 Open a telnet connection to targeted system.
387 """
Patrick Williams20f38712022-12-08 06:18:26 -0600388 self.telnet_remoteclient = TelnetRemoteclient(
389 self.hostname, self.username, self.password
390 )
Peter D Phan5963d632021-07-12 09:58:55 -0500391 if self.telnet_remoteclient.tn_remoteclient_login():
Patrick Williams20f38712022-12-08 06:18:26 -0600392 self.logger.info(
393 "\n\t[Check] %s Telnet connection established.\t [OK]"
394 % self.hostname
395 )
Peter D Phan5963d632021-07-12 09:58:55 -0500396 return True
397 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600398 self.logger.info(
399 "\n\t[Check] %s Telnet connection.\t [NOT AVAILABLE]"
400 % self.hostname
401 )
Peter D Phan5963d632021-07-12 09:58:55 -0500402 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500403
George Keishing772c9772021-06-16 23:23:42 -0500404 def generate_ffdc(self, working_protocol_list):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500405 r"""
Peter D Phan04aca3b2021-06-21 10:37:18 -0500406 Determine actions based on remote host type
Peter D Phan72ce6b82021-06-03 06:18:26 -0500407
Peter D Phan04aca3b2021-06-21 10:37:18 -0500408 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530409 working_protocol_list List of confirmed working protocols to
410 connect to remote host.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500411 """
412
Patrick Williams20f38712022-12-08 06:18:26 -0600413 self.logger.info(
414 "\n\t---- Executing commands on " + self.hostname + " ----"
415 )
416 self.logger.info(
417 "\n\tWorking protocol list: %s" % working_protocol_list
418 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500419
George Keishingf5a57502021-07-22 16:43:47 -0500420 config_dict = self.ffdc_actions
421 for target_type in config_dict.keys():
422 if self.target_type != target_type:
George Keishing6ea92b02021-07-01 11:20:50 -0500423 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500424
Peter D Phane86d9a52021-07-15 10:42:25 -0500425 self.logger.info("\n\tFFDC Path: %s " % self.ffdc_dir_path)
Patrick Williams20f38712022-12-08 06:18:26 -0600426 global_plugin_dict["global_log_store_path"] = self.ffdc_dir_path
George Keishingf5a57502021-07-22 16:43:47 -0500427 self.logger.info("\tSystem Type: %s" % target_type)
428 for k, v in config_dict[target_type].items():
Patrick Williams20f38712022-12-08 06:18:26 -0600429 if (
430 self.remote_protocol not in working_protocol_list
431 and self.remote_protocol != "ALL"
432 ):
George Keishing6ea92b02021-07-01 11:20:50 -0500433 continue
Peter D Phan72ce6b82021-06-03 06:18:26 -0500434
Patrick Williams20f38712022-12-08 06:18:26 -0600435 protocol = config_dict[target_type][k]["PROTOCOL"][0]
George Keishingf5a57502021-07-22 16:43:47 -0500436
437 if protocol not in working_protocol_list:
438 continue
439
George Keishingb7607612021-07-27 13:31:23 -0500440 if protocol in working_protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -0600441 if protocol == "SSH" or protocol == "SCP":
George Keishing12fd0652021-07-27 13:57:11 -0500442 self.protocol_ssh(protocol, target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600443 elif protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -0500444 self.protocol_telnet(target_type, k)
Patrick Williams20f38712022-12-08 06:18:26 -0600445 elif (
446 protocol == "REDFISH"
447 or protocol == "IPMI"
448 or protocol == "SHELL"
449 ):
George Keishing506b0582021-07-27 09:31:22 -0500450 self.protocol_execute(protocol, target_type, k)
George Keishingb7607612021-07-27 13:31:23 -0500451 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600452 self.logger.error(
453 "\n\tERROR: %s is not available for %s."
454 % (protocol, self.hostname)
455 )
George Keishingeafba182021-06-29 13:44:58 -0500456
Peter D Phan04aca3b2021-06-21 10:37:18 -0500457 # Close network connection after collecting all files
Patrick Williams20f38712022-12-08 06:18:26 -0600458 self.elapsed_time = time.strftime(
459 "%H:%M:%S", time.gmtime(time.time() - self.start_time)
460 )
George Keishing48972ba2025-05-05 17:40:29 +0530461 self.logger.info("\n\tTotal time taken: %s" % self.elapsed_time)
Peter D Phanbff617a2021-07-22 08:41:35 -0500462 if self.ssh_remoteclient:
463 self.ssh_remoteclient.ssh_remoteclient_disconnect()
464 if self.telnet_remoteclient:
465 self.telnet_remoteclient.tn_remoteclient_disconnect()
Peter D Phan04aca3b2021-06-21 10:37:18 -0500466
Patrick Williams20f38712022-12-08 06:18:26 -0600467 def protocol_ssh(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500468 r"""
469 Perform actions using SSH and SCP protocols.
470
471 Description of argument(s):
George Keishing12fd0652021-07-27 13:57:11 -0500472 protocol Protocol to execute.
George Keishingf5a57502021-07-22 16:43:47 -0500473 target_type OS Type of remote host.
George Keishing6ea92b02021-07-01 11:20:50 -0500474 sub_type Group type of commands.
Peter D Phan0c669772021-06-24 13:52:42 -0500475 """
476
Patrick Williams20f38712022-12-08 06:18:26 -0600477 if protocol == "SCP":
George Keishingf5a57502021-07-22 16:43:47 -0500478 self.group_copy(self.ffdc_actions[target_type][sub_type])
George Keishing6ea92b02021-07-01 11:20:50 -0500479 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600480 self.collect_and_copy_ffdc(
481 self.ffdc_actions[target_type][sub_type]
482 )
Peter D Phan0c669772021-06-24 13:52:42 -0500483
Patrick Williams20f38712022-12-08 06:18:26 -0600484 def protocol_telnet(self, target_type, sub_type):
Peter D Phan5963d632021-07-12 09:58:55 -0500485 r"""
486 Perform actions using telnet protocol.
487 Description of argument(s):
George Keishingf5a57502021-07-22 16:43:47 -0500488 target_type OS Type of remote host.
Peter D Phan5963d632021-07-12 09:58:55 -0500489 """
Patrick Williams20f38712022-12-08 06:18:26 -0600490 self.logger.info(
491 "\n\t[Run] Executing commands on %s using %s"
492 % (self.hostname, "TELNET")
493 )
Peter D Phan5963d632021-07-12 09:58:55 -0500494 telnet_files_saved = []
495 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600496 list_of_commands = self.ffdc_actions[target_type][sub_type]["COMMANDS"]
Peter D Phan5963d632021-07-12 09:58:55 -0500497 for index, each_cmd in enumerate(list_of_commands, start=0):
498 command_txt, command_timeout = self.unpack_command(each_cmd)
Patrick Williams20f38712022-12-08 06:18:26 -0600499 result = self.telnet_remoteclient.execute_command(
500 command_txt, command_timeout
501 )
Peter D Phan5963d632021-07-12 09:58:55 -0500502 if result:
503 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600504 targ_file = self.ffdc_actions[target_type][sub_type][
505 "FILES"
506 ][index]
Peter D Phan5963d632021-07-12 09:58:55 -0500507 except IndexError:
Peter D Phane86d9a52021-07-15 10:42:25 -0500508 targ_file = command_txt
509 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600510 "\n\t[WARN] Missing filename to store data from"
511 " telnet %s." % each_cmd
512 )
513 self.logger.warning(
514 "\t[WARN] Data will be stored in %s." % targ_file
515 )
516 targ_file_with_path = (
517 self.ffdc_dir_path + self.ffdc_prefix + targ_file
518 )
Peter D Phan5963d632021-07-12 09:58:55 -0500519 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600520 with open(targ_file_with_path, "w") as fp:
Peter D Phan5963d632021-07-12 09:58:55 -0500521 fp.write(result)
522 fp.close
523 telnet_files_saved.append(targ_file)
524 progress_counter += 1
525 self.print_progress(progress_counter)
Peter D Phane86d9a52021-07-15 10:42:25 -0500526 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan5963d632021-07-12 09:58:55 -0500527 for file in telnet_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500528 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
Peter D Phan5963d632021-07-12 09:58:55 -0500529
Patrick Williams20f38712022-12-08 06:18:26 -0600530 def protocol_execute(self, protocol, target_type, sub_type):
Peter D Phan0c669772021-06-24 13:52:42 -0500531 r"""
George Keishing506b0582021-07-27 09:31:22 -0500532 Perform actions for a given protocol.
Peter D Phan0c669772021-06-24 13:52:42 -0500533
534 Description of argument(s):
George Keishing506b0582021-07-27 09:31:22 -0500535 protocol Protocol to execute.
George Keishingf5a57502021-07-22 16:43:47 -0500536 target_type OS Type of remote host.
George Keishing6ea92b02021-07-01 11:20:50 -0500537 sub_type Group type of commands.
Peter D Phan0c669772021-06-24 13:52:42 -0500538 """
539
Patrick Williams20f38712022-12-08 06:18:26 -0600540 self.logger.info(
541 "\n\t[Run] Executing commands to %s using %s"
542 % (self.hostname, protocol)
543 )
George Keishing506b0582021-07-27 09:31:22 -0500544 executed_files_saved = []
George Keishingeafba182021-06-29 13:44:58 -0500545 progress_counter = 0
Patrick Williams20f38712022-12-08 06:18:26 -0600546 list_of_cmd = self.get_command_list(
547 self.ffdc_actions[target_type][sub_type]
548 )
George Keishingeafba182021-06-29 13:44:58 -0500549 for index, each_cmd in enumerate(list_of_cmd, start=0):
George Keishingcaa97e62021-08-03 14:00:09 -0500550 plugin_call = False
George Keishingb97a9042021-07-29 07:41:20 -0500551 if isinstance(each_cmd, dict):
Patrick Williams20f38712022-12-08 06:18:26 -0600552 if "plugin" in each_cmd:
George Keishing1e7b0182021-08-06 14:05:54 -0500553 # If the error is set and plugin explicitly
554 # requested to skip execution on error..
Patrick Williams20f38712022-12-08 06:18:26 -0600555 if plugin_error_dict[
556 "exit_on_error"
557 ] and self.plugin_error_check(each_cmd["plugin"]):
558 self.logger.info(
559 "\n\t[PLUGIN-ERROR] exit_on_error: %s"
560 % plugin_error_dict["exit_on_error"]
561 )
562 self.logger.info(
563 "\t[PLUGIN-SKIP] %s" % each_cmd["plugin"][0]
564 )
George Keishing1e7b0182021-08-06 14:05:54 -0500565 continue
George Keishingcaa97e62021-08-03 14:00:09 -0500566 plugin_call = True
George Keishingb97a9042021-07-29 07:41:20 -0500567 # call the plugin
568 self.logger.info("\n\t[PLUGIN-START]")
Patrick Williams20f38712022-12-08 06:18:26 -0600569 result = self.execute_plugin_block(each_cmd["plugin"])
George Keishingb97a9042021-07-29 07:41:20 -0500570 self.logger.info("\t[PLUGIN-END]\n")
George Keishingb97a9042021-07-29 07:41:20 -0500571 else:
George Keishing2b83e042021-08-03 12:56:11 -0500572 each_cmd = self.yaml_env_and_plugin_vars_populate(each_cmd)
George Keishingb97a9042021-07-29 07:41:20 -0500573
George Keishingcaa97e62021-08-03 14:00:09 -0500574 if not plugin_call:
575 result = self.run_tool_cmd(each_cmd)
George Keishingeafba182021-06-29 13:44:58 -0500576 if result:
577 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600578 file_name = self.get_file_list(
579 self.ffdc_actions[target_type][sub_type]
580 )[index]
George Keishingb97a9042021-07-29 07:41:20 -0500581 # If file is specified as None.
George Keishing0581cb02021-08-05 15:08:58 -0500582 if file_name == "None":
George Keishingb97a9042021-07-29 07:41:20 -0500583 continue
Patrick Williams20f38712022-12-08 06:18:26 -0600584 targ_file = self.yaml_env_and_plugin_vars_populate(
585 file_name
586 )
George Keishingeafba182021-06-29 13:44:58 -0500587 except IndexError:
Patrick Williams20f38712022-12-08 06:18:26 -0600588 targ_file = each_cmd.split("/")[-1]
George Keishing506b0582021-07-27 09:31:22 -0500589 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600590 "\n\t[WARN] Missing filename to store data from %s."
591 % each_cmd
592 )
593 self.logger.warning(
594 "\t[WARN] Data will be stored in %s." % targ_file
595 )
George Keishingeafba182021-06-29 13:44:58 -0500596
Patrick Williams20f38712022-12-08 06:18:26 -0600597 targ_file_with_path = (
598 self.ffdc_dir_path + self.ffdc_prefix + targ_file
599 )
George Keishingeafba182021-06-29 13:44:58 -0500600
601 # Creates a new file
Patrick Williams20f38712022-12-08 06:18:26 -0600602 with open(targ_file_with_path, "w") as fp:
George Keishing91308ea2021-08-10 14:43:15 -0500603 if isinstance(result, dict):
604 fp.write(json.dumps(result))
605 else:
606 fp.write(result)
George Keishingeafba182021-06-29 13:44:58 -0500607 fp.close
George Keishing506b0582021-07-27 09:31:22 -0500608 executed_files_saved.append(targ_file)
George Keishingeafba182021-06-29 13:44:58 -0500609
610 progress_counter += 1
611 self.print_progress(progress_counter)
612
Peter D Phane86d9a52021-07-15 10:42:25 -0500613 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
George Keishingeafba182021-06-29 13:44:58 -0500614
George Keishing506b0582021-07-27 09:31:22 -0500615 for file in executed_files_saved:
Peter D Phane86d9a52021-07-15 10:42:25 -0500616 self.logger.info("\n\t\tSuccessfully save file " + file + ".")
George Keishingeafba182021-06-29 13:44:58 -0500617
Patrick Williams20f38712022-12-08 06:18:26 -0600618 def collect_and_copy_ffdc(
619 self, ffdc_actions_for_target_type, form_filename=False
620 ):
Peter D Phan04aca3b2021-06-21 10:37:18 -0500621 r"""
622 Send commands in ffdc_config file to targeted system.
623
624 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530625 ffdc_actions_for_target_type Commands and files for the selected
626 remote host type.
627 form_filename If true, pre-pend self.target_type to
628 filename
Peter D Phan04aca3b2021-06-21 10:37:18 -0500629 """
630
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500631 # Executing commands, if any
Patrick Williams20f38712022-12-08 06:18:26 -0600632 self.ssh_execute_ffdc_commands(
633 ffdc_actions_for_target_type, form_filename
634 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500635
Peter D Phan3beb02e2021-07-06 13:25:17 -0500636 # Copying files
Peter D Phan5963d632021-07-12 09:58:55 -0500637 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600638 self.logger.info(
639 "\n\n\tCopying FFDC files from remote system %s.\n"
640 % self.hostname
641 )
Peter D Phan2b8052d2021-06-22 10:55:41 -0500642
Peter D Phan04aca3b2021-06-21 10:37:18 -0500643 # Retrieving files from target system
George Keishingf5a57502021-07-22 16:43:47 -0500644 list_of_files = self.get_file_list(ffdc_actions_for_target_type)
Patrick Williams20f38712022-12-08 06:18:26 -0600645 self.scp_ffdc(
646 self.ffdc_dir_path,
647 self.ffdc_prefix,
648 form_filename,
649 list_of_files,
650 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500651 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600652 self.logger.info(
653 "\n\n\tSkip copying FFDC files from remote system %s.\n"
654 % self.hostname
655 )
Peter D Phan04aca3b2021-06-21 10:37:18 -0500656
Patrick Williams20f38712022-12-08 06:18:26 -0600657 def get_command_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500658 r"""
659 Fetch list of commands from configuration file
660
661 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530662 ffdc_actions_for_target_type Commands and files for the selected
663 remote host type.
Peter D Phanbabf2962021-07-07 11:24:40 -0500664 """
665 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600666 list_of_commands = ffdc_actions_for_target_type["COMMANDS"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500667 except KeyError:
668 list_of_commands = []
669 return list_of_commands
670
Patrick Williams20f38712022-12-08 06:18:26 -0600671 def get_file_list(self, ffdc_actions_for_target_type):
Peter D Phanbabf2962021-07-07 11:24:40 -0500672 r"""
673 Fetch list of commands from configuration file
674
675 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530676 ffdc_actions_for_target_type Commands and files for the selected
677 remote host type.
Peter D Phanbabf2962021-07-07 11:24:40 -0500678 """
679 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600680 list_of_files = ffdc_actions_for_target_type["FILES"]
Peter D Phanbabf2962021-07-07 11:24:40 -0500681 except KeyError:
682 list_of_files = []
683 return list_of_files
684
Patrick Williams20f38712022-12-08 06:18:26 -0600685 def unpack_command(self, command):
Peter D Phan5963d632021-07-12 09:58:55 -0500686 r"""
687 Unpack command from config file
688
689 Description of argument(s):
690 command Command from config file.
691 """
692 if isinstance(command, dict):
693 command_txt = next(iter(command))
694 command_timeout = next(iter(command.values()))
695 elif isinstance(command, str):
696 command_txt = command
697 # Default command timeout 60 seconds
698 command_timeout = 60
699
700 return command_txt, command_timeout
701
Patrick Williams20f38712022-12-08 06:18:26 -0600702 def ssh_execute_ffdc_commands(
703 self, ffdc_actions_for_target_type, form_filename=False
704 ):
Peter D Phan3beb02e2021-07-06 13:25:17 -0500705 r"""
706 Send commands in ffdc_config file to targeted system.
707
708 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530709 ffdc_actions_for_target_type Commands and files for the selected
710 remote host type.
711 form_filename If true, pre-pend self.target_type to
712 filename
Peter D Phan3beb02e2021-07-06 13:25:17 -0500713 """
Patrick Williams20f38712022-12-08 06:18:26 -0600714 self.logger.info(
715 "\n\t[Run] Executing commands on %s using %s"
716 % (self.hostname, ffdc_actions_for_target_type["PROTOCOL"][0])
717 )
Peter D Phan3beb02e2021-07-06 13:25:17 -0500718
George Keishingf5a57502021-07-22 16:43:47 -0500719 list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500720 # If command list is empty, returns
721 if not list_of_commands:
722 return
723
724 progress_counter = 0
725 for command in list_of_commands:
Peter D Phan5963d632021-07-12 09:58:55 -0500726 command_txt, command_timeout = self.unpack_command(command)
Peter D Phan3beb02e2021-07-06 13:25:17 -0500727
728 if form_filename:
729 command_txt = str(command_txt % self.target_type)
730
Patrick Williams20f38712022-12-08 06:18:26 -0600731 (
732 cmd_exit_code,
733 err,
734 response,
735 ) = self.ssh_remoteclient.execute_command(
736 command_txt, command_timeout
737 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500738
739 if cmd_exit_code:
740 self.logger.warning(
Patrick Williams20f38712022-12-08 06:18:26 -0600741 "\n\t\t[WARN] %s exits with code %s."
742 % (command_txt, str(cmd_exit_code))
743 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500744 self.logger.warning("\t\t[WARN] %s " % err)
Peter D Phanbabf2962021-07-07 11:24:40 -0500745
Peter D Phan3beb02e2021-07-06 13:25:17 -0500746 progress_counter += 1
747 self.print_progress(progress_counter)
748
Peter D Phane86d9a52021-07-15 10:42:25 -0500749 self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
Peter D Phan3beb02e2021-07-06 13:25:17 -0500750
Patrick Williams20f38712022-12-08 06:18:26 -0600751 def group_copy(self, ffdc_actions_for_target_type):
Peter D Phan56429a62021-06-23 08:38:29 -0500752 r"""
753 scp group of files (wild card) from remote host.
754
755 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530756 fdc_actions_for_target_type Commands and files for the selected
757 remote host type.
Peter D Phan56429a62021-06-23 08:38:29 -0500758 """
Peter D Phan3beb02e2021-07-06 13:25:17 -0500759
Peter D Phan5963d632021-07-12 09:58:55 -0500760 if self.ssh_remoteclient.scpclient:
Patrick Williams20f38712022-12-08 06:18:26 -0600761 self.logger.info(
762 "\n\tCopying files from remote system %s via SCP.\n"
763 % self.hostname
764 )
Peter D Phan56429a62021-06-23 08:38:29 -0500765
Patrick Williams20f38712022-12-08 06:18:26 -0600766 list_of_commands = self.get_command_list(
767 ffdc_actions_for_target_type
768 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500769 # If command list is empty, returns
770 if not list_of_commands:
771 return
Peter D Phan56429a62021-06-23 08:38:29 -0500772
Peter D Phanbabf2962021-07-07 11:24:40 -0500773 for command in list_of_commands:
774 try:
George Keishingb4540e72021-08-02 13:48:46 -0500775 command = self.yaml_env_and_plugin_vars_populate(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500776 except IndexError:
George Keishingb4540e72021-08-02 13:48:46 -0500777 self.logger.error("\t\tInvalid command %s" % command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500778 continue
779
Patrick Williams20f38712022-12-08 06:18:26 -0600780 (
781 cmd_exit_code,
782 err,
783 response,
784 ) = self.ssh_remoteclient.execute_command(command)
Peter D Phanbabf2962021-07-07 11:24:40 -0500785
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500786 # If file does not exist, code take no action.
787 # cmd_exit_code is ignored for this scenario.
Peter D Phan56429a62021-06-23 08:38:29 -0500788 if response:
Patrick Williams20f38712022-12-08 06:18:26 -0600789 scp_result = self.ssh_remoteclient.scp_file_from_remote(
790 response.split("\n"), self.ffdc_dir_path
791 )
Peter D Phan56429a62021-06-23 08:38:29 -0500792 if scp_result:
Patrick Williams20f38712022-12-08 06:18:26 -0600793 self.logger.info(
794 "\t\tSuccessfully copied from "
795 + self.hostname
796 + ":"
797 + command
798 )
Peter D Phan56429a62021-06-23 08:38:29 -0500799 else:
George Keishinga56e87b2021-08-06 00:24:19 -0500800 self.logger.info("\t\t%s has no result" % command)
Peter D Phan56429a62021-06-23 08:38:29 -0500801
802 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600803 self.logger.info(
804 "\n\n\tSkip copying files from remote system %s.\n"
805 % self.hostname
806 )
Peter D Phan56429a62021-06-23 08:38:29 -0500807
Patrick Williams20f38712022-12-08 06:18:26 -0600808 def scp_ffdc(
809 self,
810 targ_dir_path,
811 targ_file_prefix,
812 form_filename,
813 file_list=None,
814 quiet=None,
815 ):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500816 r"""
George Keishingc754b432025-04-24 14:27:14 +0530817 SCP all files in file_dict to the indicated directory on the local
818 system.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500819
820 Description of argument(s):
George Keishingc754b432025-04-24 14:27:14 +0530821 targ_dir_path The path of the directory to receive
822 the files.
George Keishinge16f1582022-12-15 07:32:21 -0600823 targ_file_prefix Prefix which will be prepended to each
Peter D Phan72ce6b82021-06-03 06:18:26 -0500824 target file's name.
George Keishingc754b432025-04-24 14:27:14 +0530825 file_dict A dictionary of files to scp from
826 targeted system to this system
Peter D Phan72ce6b82021-06-03 06:18:26 -0500827
828 """
829
Peter D Phan72ce6b82021-06-03 06:18:26 -0500830 progress_counter = 0
831 for filename in file_list:
Peter D Phan2b8052d2021-06-22 10:55:41 -0500832 if form_filename:
833 filename = str(filename % self.target_type)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500834 source_file_path = filename
Patrick Williams20f38712022-12-08 06:18:26 -0600835 targ_file_path = (
836 targ_dir_path + targ_file_prefix + filename.split("/")[-1]
837 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500838
Peter D Phanbabf2962021-07-07 11:24:40 -0500839 # If source file name contains wild card, copy filename as is.
Patrick Williams20f38712022-12-08 06:18:26 -0600840 if "*" in source_file_path:
841 scp_result = self.ssh_remoteclient.scp_file_from_remote(
842 source_file_path, self.ffdc_dir_path
843 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500844 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600845 scp_result = self.ssh_remoteclient.scp_file_from_remote(
846 source_file_path, targ_file_path
847 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500848
849 if not quiet:
850 if scp_result:
Peter D Phane86d9a52021-07-15 10:42:25 -0500851 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -0600852 "\t\tSuccessfully copied from "
853 + self.hostname
854 + ":"
855 + source_file_path
856 + ".\n"
857 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500858 else:
Peter D Phane86d9a52021-07-15 10:42:25 -0500859 self.logger.info(
Patrick Williams20f38712022-12-08 06:18:26 -0600860 "\t\tFail to copy from "
861 + self.hostname
862 + ":"
863 + source_file_path
864 + ".\n"
865 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500866 else:
867 progress_counter += 1
868 self.print_progress(progress_counter)
869
Peter D Phan5e56f522021-12-20 13:19:41 -0600870 def set_ffdc_default_store_path(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500871 r"""
872 Set a default value for self.ffdc_dir_path and self.ffdc_prefix.
George Keishingc754b432025-04-24 14:27:14 +0530873 Collected ffdc file will be stored in dir
874 /self.location/hostname_timestr/.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500875 Individual ffdc file will have timestr_filename.
876
877 Description of class variables:
George Keishingc754b432025-04-24 14:27:14 +0530878 self.ffdc_dir_path The dir path where collected ffdc data files
879 should be put.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500880
881 self.ffdc_prefix The prefix to be given to each ffdc file name.
882
883 """
884
885 timestr = time.strftime("%Y%m%d-%H%M%S")
Patrick Williams20f38712022-12-08 06:18:26 -0600886 self.ffdc_dir_path = (
887 self.location + "/" + self.hostname + "_" + timestr + "/"
888 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500889 self.ffdc_prefix = timestr + "_"
890 self.validate_local_store(self.ffdc_dir_path)
891
Peter D Phan5e56f522021-12-20 13:19:41 -0600892 # Need to verify local store path exists prior to instantiate this class.
893 # This class method is used to share the same code between CLI input parm
894 # and Robot Framework "${EXECDIR}/logs" before referencing this class.
895 @classmethod
896 def validate_local_store(cls, dir_path):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500897 r"""
898 Ensure path exists to store FFDC files locally.
899
900 Description of variable:
901 dir_path The dir path where collected ffdc data files will be stored.
902
903 """
904
905 if not os.path.exists(dir_path):
906 try:
George Keishing7b3a5132021-07-13 09:24:02 -0500907 os.makedirs(dir_path, 0o755)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500908 except (IOError, OSError) as e:
909 # PermissionError
910 if e.errno == EPERM or e.errno == EACCES:
George Keishing15352052025-04-24 18:55:47 +0530911 print(
Patrick Williams20f38712022-12-08 06:18:26 -0600912 "\tERROR: os.makedirs %s failed with"
913 " PermissionError.\n" % dir_path
914 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500915 else:
George Keishing15352052025-04-24 18:55:47 +0530916 print(
Patrick Williams20f38712022-12-08 06:18:26 -0600917 "\tERROR: os.makedirs %s failed with %s.\n"
918 % (dir_path, e.strerror)
919 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500920 sys.exit(-1)
921
922 def print_progress(self, progress):
923 r"""
924 Print activity progress +
925
926 Description of variable:
927 progress Progress counter.
928
929 """
930
931 sys.stdout.write("\r\t" + "+" * progress)
932 sys.stdout.flush()
Patrick Williams20f38712022-12-08 06:18:26 -0600933 time.sleep(0.1)
Peter D Phan0c669772021-06-24 13:52:42 -0500934
935 def verify_redfish(self):
936 r"""
937 Verify remote host has redfish service active
938
939 """
Patrick Williams20f38712022-12-08 06:18:26 -0600940 redfish_parm = (
941 "redfishtool -r "
942 + self.hostname
George Keishing7a61aa22023-06-26 13:18:37 +0530943 + ":"
944 + self.port_https
Patrick Williams20f38712022-12-08 06:18:26 -0600945 + " -S Always raw GET /redfish/v1/"
946 )
947 return self.run_tool_cmd(redfish_parm, True)
Peter D Phan0c669772021-06-24 13:52:42 -0500948
George Keishingeafba182021-06-29 13:44:58 -0500949 def verify_ipmi(self):
950 r"""
951 Verify remote host has IPMI LAN service active
952
953 """
Patrick Williams20f38712022-12-08 06:18:26 -0600954 if self.target_type == "OPENBMC":
955 ipmi_parm = (
956 "ipmitool -I lanplus -C 17 -U "
957 + self.username
958 + " -P "
959 + self.password
960 + " -H "
961 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +0530962 + " -p "
963 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -0600964 + " power status"
965 )
George Keishing484f8242021-07-27 01:42:02 -0500966 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600967 ipmi_parm = (
968 "ipmitool -I lanplus -P "
969 + self.password
970 + " -H "
971 + self.hostname
George Keishinge8a41752023-06-22 21:42:47 +0530972 + " -p "
973 + str(self.port_ipmi)
Patrick Williams20f38712022-12-08 06:18:26 -0600974 + " power status"
975 )
George Keishing484f8242021-07-27 01:42:02 -0500976
Patrick Williams20f38712022-12-08 06:18:26 -0600977 return self.run_tool_cmd(ipmi_parm, True)
George Keishingeafba182021-06-29 13:44:58 -0500978
Patrick Williams20f38712022-12-08 06:18:26 -0600979 def run_tool_cmd(self, parms_string, quiet=False):
George Keishingeafba182021-06-29 13:44:58 -0500980 r"""
George Keishing506b0582021-07-27 09:31:22 -0500981 Run CLI standard tool or scripts.
George Keishingeafba182021-06-29 13:44:58 -0500982
983 Description of variable:
George Keishing506b0582021-07-27 09:31:22 -0500984 parms_string tool command options.
985 quiet do not print tool error message if True
George Keishingeafba182021-06-29 13:44:58 -0500986 """
987
Patrick Williams20f38712022-12-08 06:18:26 -0600988 result = subprocess.run(
989 [parms_string],
990 stdout=subprocess.PIPE,
991 stderr=subprocess.PIPE,
992 shell=True,
993 universal_newlines=True,
994 )
George Keishingeafba182021-06-29 13:44:58 -0500995
996 if result.stderr and not quiet:
George Keishing0e9b5ba2025-05-08 12:17:58 +0530997 if self.password in parms_string:
998 parms_string = parms_string.replace(self.password, "********")
Patrick Williams20f38712022-12-08 06:18:26 -0600999 self.logger.error("\n\t\tERROR with %s " % parms_string)
1000 self.logger.error("\t\t" + result.stderr)
George Keishingeafba182021-06-29 13:44:58 -05001001
1002 return result.stdout
George Keishing04d29102021-07-16 02:05:57 -05001003
George Keishingf5a57502021-07-22 16:43:47 -05001004 def verify_protocol(self, protocol_list):
1005 r"""
1006 Perform protocol working check.
1007
1008 Description of argument(s):
1009 protocol_list List of protocol.
1010 """
1011
1012 tmp_list = []
1013 if self.target_is_pingable():
1014 tmp_list.append("SHELL")
1015
1016 for protocol in protocol_list:
Patrick Williams20f38712022-12-08 06:18:26 -06001017 if self.remote_protocol != "ALL":
George Keishingf5a57502021-07-22 16:43:47 -05001018 if self.remote_protocol != protocol:
1019 continue
1020
1021 # Only check SSH/SCP once for both protocols
Patrick Williams20f38712022-12-08 06:18:26 -06001022 if (
1023 protocol == "SSH"
1024 or protocol == "SCP"
1025 and protocol not in tmp_list
1026 ):
George Keishingf5a57502021-07-22 16:43:47 -05001027 if self.ssh_to_target_system():
George Keishingaa638702021-07-26 11:48:28 -05001028 # Add only what user asked.
Patrick Williams20f38712022-12-08 06:18:26 -06001029 if self.remote_protocol != "ALL":
George Keishingaa638702021-07-26 11:48:28 -05001030 tmp_list.append(self.remote_protocol)
1031 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001032 tmp_list.append("SSH")
1033 tmp_list.append("SCP")
George Keishingf5a57502021-07-22 16:43:47 -05001034
Patrick Williams20f38712022-12-08 06:18:26 -06001035 if protocol == "TELNET":
George Keishingf5a57502021-07-22 16:43:47 -05001036 if self.telnet_to_target_system():
1037 tmp_list.append(protocol)
1038
Patrick Williams20f38712022-12-08 06:18:26 -06001039 if protocol == "REDFISH":
George Keishingf5a57502021-07-22 16:43:47 -05001040 if self.verify_redfish():
1041 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001042 self.logger.info(
1043 "\n\t[Check] %s Redfish Service.\t\t [OK]"
1044 % self.hostname
1045 )
George Keishingf5a57502021-07-22 16:43:47 -05001046 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001047 self.logger.info(
1048 "\n\t[Check] %s Redfish Service.\t\t [NOT AVAILABLE]"
1049 % self.hostname
1050 )
George Keishingf5a57502021-07-22 16:43:47 -05001051
Patrick Williams20f38712022-12-08 06:18:26 -06001052 if protocol == "IPMI":
George Keishingf5a57502021-07-22 16:43:47 -05001053 if self.verify_ipmi():
1054 tmp_list.append(protocol)
Patrick Williams20f38712022-12-08 06:18:26 -06001055 self.logger.info(
1056 "\n\t[Check] %s IPMI LAN Service.\t\t [OK]"
1057 % self.hostname
1058 )
George Keishingf5a57502021-07-22 16:43:47 -05001059 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001060 self.logger.info(
1061 "\n\t[Check] %s IPMI LAN Service.\t\t [NOT AVAILABLE]"
1062 % self.hostname
1063 )
George Keishingf5a57502021-07-22 16:43:47 -05001064
1065 return tmp_list
George Keishinge1686752021-07-27 12:55:28 -05001066
1067 def load_env(self):
1068 r"""
George Keishing0e9b5ba2025-05-08 12:17:58 +05301069 Load the user environment variables from a YAML file.
George Keishinge1686752021-07-27 12:55:28 -05001070
George Keishing0e9b5ba2025-05-08 12:17:58 +05301071 This method reads the environment variables from a YAML file specified
1072 in the ENV_FILE environment variable. If the file is not found or
1073 there is an error reading the file, an exception is raised.
1074
1075 The YAML file should have the following format:
1076
1077 .. code-block:: yaml
1078
1079 VAR_NAME: VAR_VALUE
1080
1081 Where VAR_NAME is the name of the environment variable, and
1082 VAR_VALUE is its value.
1083
1084 After loading the environment variables, they are stored in the
1085 self.env attribute for later use.
George Keishinge1686752021-07-27 12:55:28 -05001086 """
George Keishing0e9b5ba2025-05-08 12:17:58 +05301087
Patrick Williams20f38712022-12-08 06:18:26 -06001088 os.environ["hostname"] = self.hostname
1089 os.environ["username"] = self.username
1090 os.environ["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301091 os.environ["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301092 os.environ["port_https"] = self.port_https
1093 os.environ["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001094
1095 # Append default Env.
Patrick Williams20f38712022-12-08 06:18:26 -06001096 self.env_dict["hostname"] = self.hostname
1097 self.env_dict["username"] = self.username
1098 self.env_dict["password"] = self.password
George Keishing7a61aa22023-06-26 13:18:37 +05301099 self.env_dict["port_ssh"] = self.port_ssh
George Keishinge8a41752023-06-22 21:42:47 +05301100 self.env_dict["port_https"] = self.port_https
1101 self.env_dict["port_ipmi"] = self.port_ipmi
George Keishinge1686752021-07-27 12:55:28 -05001102
1103 try:
1104 tmp_env_dict = {}
1105 if self.env_vars:
1106 tmp_env_dict = json.loads(self.env_vars)
1107 # Export ENV vars default.
1108 for key, value in tmp_env_dict.items():
1109 os.environ[key] = value
1110 self.env_dict[key] = str(value)
1111
George Keishing0e9b5ba2025-05-08 12:17:58 +05301112 # Load user specified ENV config YAML.
George Keishinge1686752021-07-27 12:55:28 -05001113 if self.econfig:
Patrick Williams20f38712022-12-08 06:18:26 -06001114 with open(self.econfig, "r") as file:
George Keishinge9b23d32021-08-13 12:57:58 -05001115 try:
Yunyun Linf87cc0a2022-06-08 16:57:04 -07001116 tmp_env_dict = yaml.load(file, Loader=yaml.SafeLoader)
George Keishinge9b23d32021-08-13 12:57:58 -05001117 except yaml.YAMLError as e:
1118 self.logger.error(e)
1119 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001120 # Export ENV vars.
Patrick Williams20f38712022-12-08 06:18:26 -06001121 for key, value in tmp_env_dict["env_params"].items():
George Keishinge1686752021-07-27 12:55:28 -05001122 os.environ[key] = str(value)
1123 self.env_dict[key] = str(value)
1124 except json.decoder.JSONDecodeError as e:
1125 self.logger.error("\n\tERROR: %s " % e)
1126 sys.exit(-1)
George Keishing0e9b5ba2025-05-08 12:17:58 +05301127 except FileNotFoundError as e:
1128 self.logger.error("\n\tERROR: %s " % e)
1129 sys.exit(-1)
George Keishinge1686752021-07-27 12:55:28 -05001130
1131 # This to mask the password from displaying on the console.
1132 mask_dict = self.env_dict.copy()
1133 for k, v in mask_dict.items():
1134 if k.lower().find("password") != -1:
1135 hidden_text = []
1136 hidden_text.append(v)
Patrick Williams20f38712022-12-08 06:18:26 -06001137 password_regex = (
1138 "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1139 )
George Keishinge1686752021-07-27 12:55:28 -05001140 mask_dict[k] = re.sub(password_regex, "********", v)
1141
1142 self.logger.info(json.dumps(mask_dict, indent=8, sort_keys=False))
George Keishingb97a9042021-07-29 07:41:20 -05001143
1144 def execute_python_eval(self, eval_string):
1145 r"""
George Keishing9348b402021-08-13 12:22:35 -05001146 Execute qualified python function string using eval.
George Keishingb97a9042021-07-29 07:41:20 -05001147
1148 Description of argument(s):
1149 eval_string Execute the python object.
1150
1151 Example:
1152 eval(plugin.foo_func.foo_func(10))
1153 """
1154 try:
George Keishingdda48ce2021-08-12 07:02:27 -05001155 self.logger.info("\tExecuting plugin func()")
1156 self.logger.debug("\tCall func: %s" % eval_string)
George Keishingb97a9042021-07-29 07:41:20 -05001157 result = eval(eval_string)
1158 self.logger.info("\treturn: %s" % str(result))
Patrick Williams20f38712022-12-08 06:18:26 -06001159 except (
1160 ValueError,
1161 SyntaxError,
1162 NameError,
1163 AttributeError,
1164 TypeError,
1165 ) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001166 self.logger.error("\tERROR: execute_python_eval: %s" % e)
1167 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001168 plugin_error_dict["exit_on_error"] = True
George Keishing73b95d12021-08-13 14:30:52 -05001169 self.logger.info("\treturn: PLUGIN_EVAL_ERROR")
Patrick Williams20f38712022-12-08 06:18:26 -06001170 return "PLUGIN_EVAL_ERROR"
George Keishingb97a9042021-07-29 07:41:20 -05001171
1172 return result
1173
1174 def execute_plugin_block(self, plugin_cmd_list):
1175 r"""
Peter D Phan5e56f522021-12-20 13:19:41 -06001176 Pack the plugin command to qualifed python string object.
George Keishingb97a9042021-07-29 07:41:20 -05001177
1178 Description of argument(s):
1179 plugin_list_dict Plugin block read from YAML
1180 [{'plugin_name': 'plugin.foo_func.my_func'},
1181 {'plugin_args': [10]}]
1182
1183 Example:
1184 - plugin:
1185 - plugin_name: plugin.foo_func.my_func
1186 - plugin_args:
1187 - arg1
1188 - arg2
1189
1190 - plugin:
1191 - plugin_name: result = plugin.foo_func.my_func
1192 - plugin_args:
1193 - arg1
1194 - arg2
1195
1196 - plugin:
1197 - plugin_name: result1,result2 = plugin.foo_func.my_func
1198 - plugin_args:
1199 - arg1
1200 - arg2
1201 """
1202 try:
Patrick Williams20f38712022-12-08 06:18:26 -06001203 idx = self.key_index_list_dict("plugin_name", plugin_cmd_list)
1204 plugin_name = plugin_cmd_list[idx]["plugin_name"]
George Keishingb97a9042021-07-29 07:41:20 -05001205 # Equal separator means plugin function returns result.
Patrick Williams20f38712022-12-08 06:18:26 -06001206 if " = " in plugin_name:
George Keishingb97a9042021-07-29 07:41:20 -05001207 # Ex. ['result', 'plugin.foo_func.my_func']
Patrick Williams20f38712022-12-08 06:18:26 -06001208 plugin_name_args = plugin_name.split(" = ")
George Keishingb97a9042021-07-29 07:41:20 -05001209 # plugin func return data.
1210 for arg in plugin_name_args:
1211 if arg == plugin_name_args[-1]:
1212 plugin_name = arg
1213 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001214 plugin_resp = arg.split(",")
George Keishingb97a9042021-07-29 07:41:20 -05001215 # ['result1','result2']
1216 for x in plugin_resp:
1217 global_plugin_list.append(x)
1218 global_plugin_dict[x] = ""
1219
1220 # Walk the plugin args ['arg1,'arg2']
1221 # If the YAML plugin statement 'plugin_args' is not declared.
Patrick Williams20f38712022-12-08 06:18:26 -06001222 if any("plugin_args" in d for d in plugin_cmd_list):
1223 idx = self.key_index_list_dict("plugin_args", plugin_cmd_list)
1224 plugin_args = plugin_cmd_list[idx]["plugin_args"]
George Keishingb97a9042021-07-29 07:41:20 -05001225 if plugin_args:
1226 plugin_args = self.yaml_args_populate(plugin_args)
1227 else:
1228 plugin_args = []
1229 else:
1230 plugin_args = self.yaml_args_populate([])
1231
1232 # Pack the args arg1, arg2, .... argn into
1233 # "arg1","arg2","argn" string as params for function.
1234 parm_args_str = self.yaml_args_string(plugin_args)
1235 if parm_args_str:
Patrick Williams20f38712022-12-08 06:18:26 -06001236 plugin_func = plugin_name + "(" + parm_args_str + ")"
George Keishingb97a9042021-07-29 07:41:20 -05001237 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001238 plugin_func = plugin_name + "()"
George Keishingb97a9042021-07-29 07:41:20 -05001239
1240 # Execute plugin function.
1241 if global_plugin_dict:
1242 resp = self.execute_python_eval(plugin_func)
George Keishing9348b402021-08-13 12:22:35 -05001243 # Update plugin vars dict if there is any.
Patrick Williams20f38712022-12-08 06:18:26 -06001244 if resp != "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001245 self.response_args_data(resp)
George Keishingb97a9042021-07-29 07:41:20 -05001246 else:
George Keishingcaa97e62021-08-03 14:00:09 -05001247 resp = self.execute_python_eval(plugin_func)
George Keishingb97a9042021-07-29 07:41:20 -05001248 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001249 # Set the plugin error state.
Patrick Williams20f38712022-12-08 06:18:26 -06001250 plugin_error_dict["exit_on_error"] = True
George Keishing1e7b0182021-08-06 14:05:54 -05001251 self.logger.error("\tERROR: execute_plugin_block: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001252 pass
1253
George Keishing73b95d12021-08-13 14:30:52 -05001254 # There is a real error executing the plugin function.
Patrick Williams20f38712022-12-08 06:18:26 -06001255 if resp == "PLUGIN_EVAL_ERROR":
George Keishing73b95d12021-08-13 14:30:52 -05001256 return resp
1257
George Keishingde79a9b2021-08-12 16:14:43 -05001258 # Check if plugin_expects_return (int, string, list,dict etc)
Patrick Williams20f38712022-12-08 06:18:26 -06001259 if any("plugin_expects_return" in d for d in plugin_cmd_list):
1260 idx = self.key_index_list_dict(
1261 "plugin_expects_return", plugin_cmd_list
1262 )
1263 plugin_expects = plugin_cmd_list[idx]["plugin_expects_return"]
George Keishingde79a9b2021-08-12 16:14:43 -05001264 if plugin_expects:
1265 if resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001266 if (
1267 self.plugin_expect_type(plugin_expects, resp)
1268 == "INVALID"
1269 ):
George Keishingde79a9b2021-08-12 16:14:43 -05001270 self.logger.error("\tWARN: Plugin error check skipped")
1271 elif not self.plugin_expect_type(plugin_expects, resp):
Patrick Williams20f38712022-12-08 06:18:26 -06001272 self.logger.error(
1273 "\tERROR: Plugin expects return data: %s"
1274 % plugin_expects
1275 )
1276 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001277 elif not resp:
Patrick Williams20f38712022-12-08 06:18:26 -06001278 self.logger.error(
1279 "\tERROR: Plugin func failed to return data"
1280 )
1281 plugin_error_dict["exit_on_error"] = True
George Keishingde79a9b2021-08-12 16:14:43 -05001282
1283 return resp
1284
George Keishingb97a9042021-07-29 07:41:20 -05001285 def response_args_data(self, plugin_resp):
1286 r"""
George Keishing9348b402021-08-13 12:22:35 -05001287 Parse the plugin function response and update plugin return variable.
George Keishingb97a9042021-07-29 07:41:20 -05001288
1289 plugin_resp Response data from plugin function.
1290 """
1291 resp_list = []
George Keishing5765f792021-08-02 13:08:53 -05001292 resp_data = ""
George Keishing9348b402021-08-13 12:22:35 -05001293
George Keishingb97a9042021-07-29 07:41:20 -05001294 # There is nothing to update the plugin response.
Patrick Williams20f38712022-12-08 06:18:26 -06001295 if len(global_plugin_list) == 0 or plugin_resp == "None":
George Keishingb97a9042021-07-29 07:41:20 -05001296 return
1297
George Keishing5765f792021-08-02 13:08:53 -05001298 if isinstance(plugin_resp, str):
Patrick Williams20f38712022-12-08 06:18:26 -06001299 resp_data = plugin_resp.strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001300 resp_list.append(resp_data)
1301 elif isinstance(plugin_resp, bytes):
Patrick Williams20f38712022-12-08 06:18:26 -06001302 resp_data = str(plugin_resp, "UTF-8").strip("\r\n\t")
George Keishing5765f792021-08-02 13:08:53 -05001303 resp_list.append(resp_data)
1304 elif isinstance(plugin_resp, tuple):
1305 if len(global_plugin_list) == 1:
George Keishingb97a9042021-07-29 07:41:20 -05001306 resp_list.append(plugin_resp)
George Keishing5765f792021-08-02 13:08:53 -05001307 else:
1308 resp_list = list(plugin_resp)
Patrick Williams20f38712022-12-08 06:18:26 -06001309 resp_list = [x.strip("\r\n\t") for x in resp_list]
George Keishingb97a9042021-07-29 07:41:20 -05001310 elif isinstance(plugin_resp, list):
George Keishing5765f792021-08-02 13:08:53 -05001311 if len(global_plugin_list) == 1:
Patrick Williams20f38712022-12-08 06:18:26 -06001312 resp_list.append([x.strip("\r\n\t") for x in plugin_resp])
George Keishing5765f792021-08-02 13:08:53 -05001313 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001314 resp_list = [x.strip("\r\n\t") for x in plugin_resp]
George Keishing5765f792021-08-02 13:08:53 -05001315 elif isinstance(plugin_resp, int) or isinstance(plugin_resp, float):
1316 resp_list.append(plugin_resp)
George Keishingb97a9042021-07-29 07:41:20 -05001317
George Keishing9348b402021-08-13 12:22:35 -05001318 # Iterate if there is a list of plugin return vars to update.
George Keishingb97a9042021-07-29 07:41:20 -05001319 for idx, item in enumerate(resp_list, start=0):
George Keishing9348b402021-08-13 12:22:35 -05001320 # Exit loop, done required loop.
George Keishingb97a9042021-07-29 07:41:20 -05001321 if idx >= len(global_plugin_list):
1322 break
1323 # Find the index of the return func in the list and
1324 # update the global func return dictionary.
1325 try:
1326 dict_idx = global_plugin_list[idx]
1327 global_plugin_dict[dict_idx] = item
1328 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001329 self.logger.warn("\tWARN: response_args_data: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001330 pass
1331
1332 # Done updating plugin dict irrespective of pass or failed,
George Keishing9348b402021-08-13 12:22:35 -05001333 # clear all the list element for next plugin block execute.
George Keishingb97a9042021-07-29 07:41:20 -05001334 global_plugin_list.clear()
1335
1336 def yaml_args_string(self, plugin_args):
1337 r"""
1338 Pack the args into string.
1339
1340 plugin_args arg list ['arg1','arg2,'argn']
1341 """
Patrick Williams20f38712022-12-08 06:18:26 -06001342 args_str = ""
George Keishingb97a9042021-07-29 07:41:20 -05001343 for args in plugin_args:
1344 if args:
George Keishing0581cb02021-08-05 15:08:58 -05001345 if isinstance(args, (int, float)):
George Keishingb97a9042021-07-29 07:41:20 -05001346 args_str += str(args)
George Keishing0581cb02021-08-05 15:08:58 -05001347 elif args in global_plugin_type_list:
1348 args_str += str(global_plugin_dict[args])
George Keishingb97a9042021-07-29 07:41:20 -05001349 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001350 args_str += '"' + str(args.strip("\r\n\t")) + '"'
George Keishingb97a9042021-07-29 07:41:20 -05001351 # Skip last list element.
1352 if args != plugin_args[-1]:
1353 args_str += ","
1354 return args_str
1355
1356 def yaml_args_populate(self, yaml_arg_list):
1357 r"""
George Keishing9348b402021-08-13 12:22:35 -05001358 Decode env and plugin vars and populate.
George Keishingb97a9042021-07-29 07:41:20 -05001359
1360 Description of argument(s):
1361 yaml_arg_list arg list read from YAML
1362
1363 Example:
1364 - plugin_args:
1365 - arg1
1366 - arg2
1367
1368 yaml_arg_list: [arg2, arg2]
1369 """
1370 # Get the env loaded keys as list ['hostname', 'username', 'password'].
George Keishingb97a9042021-07-29 07:41:20 -05001371
1372 if isinstance(yaml_arg_list, list):
1373 tmp_list = []
1374 for arg in yaml_arg_list:
George Keishing0581cb02021-08-05 15:08:58 -05001375 if isinstance(arg, (int, float)):
George Keishingb97a9042021-07-29 07:41:20 -05001376 tmp_list.append(arg)
1377 continue
1378 elif isinstance(arg, str):
1379 arg_str = self.yaml_env_and_plugin_vars_populate(str(arg))
1380 tmp_list.append(arg_str)
1381 else:
1382 tmp_list.append(arg)
1383
1384 # return populated list.
1385 return tmp_list
1386
1387 def yaml_env_and_plugin_vars_populate(self, yaml_arg_str):
1388 r"""
George Keishing9348b402021-08-13 12:22:35 -05001389 Update ${MY_VAR} and plugin vars.
George Keishingb97a9042021-07-29 07:41:20 -05001390
1391 Description of argument(s):
George Keishing9348b402021-08-13 12:22:35 -05001392 yaml_arg_str arg string read from YAML.
George Keishingb97a9042021-07-29 07:41:20 -05001393
1394 Example:
1395 - cat ${MY_VAR}
1396 - ls -AX my_plugin_var
1397 """
George Keishing9348b402021-08-13 12:22:35 -05001398 # Parse the string for env vars ${env_vars}.
George Keishingb97a9042021-07-29 07:41:20 -05001399 try:
George Keishingc754b432025-04-24 14:27:14 +05301400 # Example, list of matching
1401 # env vars ['username', 'password', 'hostname']
George Keishingb97a9042021-07-29 07:41:20 -05001402 # Extra escape \ for special symbols. '\$\{([^\}]+)\}' works good.
Patrick Williams20f38712022-12-08 06:18:26 -06001403 var_name_regex = "\\$\\{([^\\}]+)\\}"
George Keishingb97a9042021-07-29 07:41:20 -05001404 env_var_names_list = re.findall(var_name_regex, yaml_arg_str)
1405 for var in env_var_names_list:
1406 env_var = os.environ[var]
Patrick Williams20f38712022-12-08 06:18:26 -06001407 env_replace = "${" + var + "}"
George Keishingb97a9042021-07-29 07:41:20 -05001408 yaml_arg_str = yaml_arg_str.replace(env_replace, env_var)
1409 except Exception as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001410 self.logger.error("\tERROR:yaml_env_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001411 pass
1412
1413 # Parse the string for plugin vars.
1414 try:
1415 # Example, list of plugin vars ['my_username', 'my_data']
1416 plugin_var_name_list = global_plugin_dict.keys()
1417 for var in plugin_var_name_list:
George Keishing9348b402021-08-13 12:22:35 -05001418 # skip env var list already populated above code block list.
George Keishing0581cb02021-08-05 15:08:58 -05001419 if var in env_var_names_list:
1420 continue
George Keishing9348b402021-08-13 12:22:35 -05001421 # If this plugin var exist but empty in dict, don't replace.
George Keishing0581cb02021-08-05 15:08:58 -05001422 # This is either a YAML plugin statement incorrectly used or
George Keishing9348b402021-08-13 12:22:35 -05001423 # user added a plugin var which is not going to be populated.
George Keishing0581cb02021-08-05 15:08:58 -05001424 if yaml_arg_str in global_plugin_dict:
1425 if isinstance(global_plugin_dict[var], (list, dict)):
George Keishingc754b432025-04-24 14:27:14 +05301426 # List data type or dict can't be replaced, use
1427 # directly in eval function call.
George Keishing0581cb02021-08-05 15:08:58 -05001428 global_plugin_type_list.append(var)
1429 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001430 yaml_arg_str = yaml_arg_str.replace(
1431 str(var), str(global_plugin_dict[var])
1432 )
George Keishing0581cb02021-08-05 15:08:58 -05001433 # Just a string like filename or command.
1434 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001435 yaml_arg_str = yaml_arg_str.replace(
1436 str(var), str(global_plugin_dict[var])
1437 )
George Keishingb97a9042021-07-29 07:41:20 -05001438 except (IndexError, ValueError) as e:
George Keishing1e7b0182021-08-06 14:05:54 -05001439 self.logger.error("\tERROR: yaml_plugin_vars_populate: %s" % e)
George Keishingb97a9042021-07-29 07:41:20 -05001440 pass
1441
1442 return yaml_arg_str
George Keishing1e7b0182021-08-06 14:05:54 -05001443
1444 def plugin_error_check(self, plugin_dict):
1445 r"""
1446 Plugin error dict processing.
1447
1448 Description of argument(s):
1449 plugin_dict Dictionary of plugin error.
1450 """
Patrick Williams20f38712022-12-08 06:18:26 -06001451 if any("plugin_error" in d for d in plugin_dict):
George Keishing1e7b0182021-08-06 14:05:54 -05001452 for d in plugin_dict:
Patrick Williams20f38712022-12-08 06:18:26 -06001453 if "plugin_error" in d:
1454 value = d["plugin_error"]
George Keishing1e7b0182021-08-06 14:05:54 -05001455 # Reference if the error is set or not by plugin.
1456 return plugin_error_dict[value]
George Keishingde79a9b2021-08-12 16:14:43 -05001457
1458 def key_index_list_dict(self, key, list_dict):
1459 r"""
1460 Iterate list of dictionary and return index if the key match is found.
1461
1462 Description of argument(s):
1463 key Valid Key in a dict.
1464 list_dict list of dictionary.
1465 """
1466 for i, d in enumerate(list_dict):
1467 if key in d.keys():
1468 return i
1469
1470 def plugin_expect_type(self, type, data):
1471 r"""
1472 Plugin expect directive type check.
1473 """
Patrick Williams20f38712022-12-08 06:18:26 -06001474 if type == "int":
George Keishingde79a9b2021-08-12 16:14:43 -05001475 return isinstance(data, int)
Patrick Williams20f38712022-12-08 06:18:26 -06001476 elif type == "float":
George Keishingde79a9b2021-08-12 16:14:43 -05001477 return isinstance(data, float)
Patrick Williams20f38712022-12-08 06:18:26 -06001478 elif type == "str":
George Keishingde79a9b2021-08-12 16:14:43 -05001479 return isinstance(data, str)
Patrick Williams20f38712022-12-08 06:18:26 -06001480 elif type == "list":
George Keishingde79a9b2021-08-12 16:14:43 -05001481 return isinstance(data, list)
Patrick Williams20f38712022-12-08 06:18:26 -06001482 elif type == "dict":
George Keishingde79a9b2021-08-12 16:14:43 -05001483 return isinstance(data, dict)
Patrick Williams20f38712022-12-08 06:18:26 -06001484 elif type == "tuple":
George Keishingde79a9b2021-08-12 16:14:43 -05001485 return isinstance(data, tuple)
1486 else:
1487 self.logger.info("\tInvalid data type requested: %s" % type)
Patrick Williams20f38712022-12-08 06:18:26 -06001488 return "INVALID"