1#!/usr/bin/env python
2
3r"""
4See class prolog below for details.
5"""
6
7import os
8import sys
9import yaml
10import json
11import time
12import logging
13import platform
14from errno import EACCES, EPERM
15import subprocess
16from ssh_utility import SSHRemoteclient
17from telnet_utility import TelnetRemoteclient
18
19
20class FFDCCollector:
21
22    r"""
23    Sends commands from configuration file to the targeted system to collect log files.
24    Fetch and store generated files at the specified location.
25
26    """
27
28    def __init__(self,
29                 hostname,
30                 username,
31                 password,
32                 ffdc_config,
33                 location,
34                 remote_type,
35                 remote_protocol,
36                 env_vars,
37                 log_level):
38        r"""
39        Description of argument(s):
40
41        hostname                name/ip of the targeted (remote) system
42        username                user on the targeted system with access to FFDC files
43        password                password for user on targeted system
44        ffdc_config             configuration file listing commands and files for FFDC
45        location                where to store collected FFDC
46        remote_type             os type of the remote host
47
48        """
49
50        self.hostname = hostname
51        self.username = username
52        self.password = password
53        # This is for the env vars a user can use in YAML to load it at runtime.
54        # Example YAML:
55        # -COMMANDS:
56        #    - my_command ${hostname}  ${username}   ${password}
57        os.environ['hostname'] = hostname
58        os.environ['username'] = username
59        os.environ['password'] = password
60
61        self.ffdc_config = ffdc_config
62        self.location = location + "/" + remote_type.upper()
63        self.ssh_remoteclient = None
64        self.telnet_remoteclient = None
65        self.ffdc_dir_path = ""
66        self.ffdc_prefix = ""
67        self.target_type = remote_type.upper()
68        self.remote_protocol = remote_protocol.upper()
69        self.start_time = 0
70        self.elapsed_time = ''
71        self.logger = None
72
73        # Set prefix values for scp files and directory.
74        # Since the time stamp is at second granularity, these values are set here
75        # to be sure that all files for this run will have same timestamps
76        # and they will be saved in the same directory.
77        # self.location == local system for now
78        self.set_ffdc_defaults()
79
80        # Logger for this run.  Need to be after set_ffdc_defaults()
81        self.script_logging(getattr(logging, log_level.upper()))
82
83        # Verify top level directory exists for storage
84        self.validate_local_store(self.location)
85
86        if self.verify_script_env():
87            # Load default or user define YAML configuration file.
88            with open(self.ffdc_config, 'r') as file:
89                self.ffdc_actions = yaml.load(file, Loader=yaml.FullLoader)
90
91            if self.target_type not in self.ffdc_actions.keys():
92                self.logger.error(
93                    "\n\tERROR: %s is not listed in %s.\n\n" % (self.target_type, self.ffdc_config))
94                sys.exit(-1)
95        else:
96            sys.exit(-1)
97
98        # Load ENV vars from user.
99        self.logger.info("\n\tENV: User define input YAML variables")
100        self.env_dict = {}
101
102        try:
103            if env_vars:
104                self.env_dict = json.loads(env_vars)
105
106                # Export ENV vars default.
107                for key, value in self.env_dict.items():
108                    os.environ[key] = value
109
110        except json.decoder.JSONDecodeError as e:
111            self.logger.error("\n\tERROR: %s " % e)
112            sys.exit(-1)
113
114        # Append default Env.
115        self.env_dict['hostname'] = self.hostname
116        self.env_dict['username'] = self.username
117        self.env_dict['password'] = self.password
118        self.logger.info(json.dumps(self.env_dict, indent=8, sort_keys=True))
119
120    def verify_script_env(self):
121
122        # Import to log version
123        import click
124        import paramiko
125
126        run_env_ok = True
127
128        redfishtool_version = self.run_redfishtool('-V').split(' ')[2].strip('\n')
129        ipmitool_version = self.run_ipmitool('-V').split(' ')[2]
130
131        self.logger.info("\n\t---- Script host environment ----")
132        self.logger.info("\t{:<10}  {:<10}".format('Script hostname', os.uname()[1]))
133        self.logger.info("\t{:<10}  {:<10}".format('Script host os', platform.platform()))
134        self.logger.info("\t{:<10}  {:>10}".format('Python', platform.python_version()))
135        self.logger.info("\t{:<10}  {:>10}".format('PyYAML', yaml.__version__))
136        self.logger.info("\t{:<10}  {:>10}".format('click', click.__version__))
137        self.logger.info("\t{:<10}  {:>10}".format('paramiko', paramiko.__version__))
138        self.logger.info("\t{:<10}  {:>9}".format('redfishtool', redfishtool_version))
139        self.logger.info("\t{:<10}  {:>12}".format('ipmitool', ipmitool_version))
140
141        if eval(yaml.__version__.replace('.', ',')) < (5, 4, 1):
142            self.logger.error("\n\tERROR: Python or python packages do not meet minimum version requirement.")
143            self.logger.error("\tERROR: PyYAML version 5.4.1 or higher is needed.\n")
144            run_env_ok = False
145
146        self.logger.info("\t---- End script host environment ----")
147        return run_env_ok
148
149    def script_logging(self,
150                       log_level_attr):
151        r"""
152        Create logger
153
154        """
155        self.logger = logging.getLogger()
156        self.logger.setLevel(log_level_attr)
157        log_file_handler = logging.FileHandler(self.ffdc_dir_path + "collector.log")
158
159        stdout_handler = logging.StreamHandler(sys.stdout)
160        self.logger.addHandler(log_file_handler)
161        self.logger.addHandler(stdout_handler)
162
163        # Turn off paramiko INFO logging
164        logging.getLogger("paramiko").setLevel(logging.WARNING)
165
166    def target_is_pingable(self):
167        r"""
168        Check if target system is ping-able.
169
170        """
171        response = os.system("ping -c 1 %s  2>&1 >/dev/null" % self.hostname)
172        if response == 0:
173            self.logger.info("\n\t[Check] %s is ping-able.\t\t [OK]" % self.hostname)
174            return True
175        else:
176            self.logger.error(
177                "\n\tERROR: %s is not ping-able. FFDC collection aborted.\n" % self.hostname)
178            sys.exit(-1)
179
180    def collect_ffdc(self):
181        r"""
182        Initiate FFDC Collection depending on requested protocol.
183
184        """
185
186        self.logger.info("\n\t---- Start communicating with %s ----" % self.hostname)
187        self.start_time = time.time()
188
189        # Find the list of target and protocol supported.
190        check_protocol_list = []
191        config_dict = self.ffdc_actions
192
193        for target_type in config_dict.keys():
194            if self.target_type != target_type:
195                continue
196
197            for k, v in config_dict[target_type].items():
198                if config_dict[target_type][k]['PROTOCOL'][0] not in check_protocol_list:
199                    check_protocol_list.append(config_dict[target_type][k]['PROTOCOL'][0])
200
201        self.logger.info("\n\t %s protocol type: %s" % (self.target_type, check_protocol_list))
202
203        verified_working_protocol = self.verify_protocol(check_protocol_list)
204
205        if verified_working_protocol:
206            self.logger.info("\n\t---- Completed protocol pre-requisite check ----\n")
207
208        # Verify top level directory exists for storage
209        self.validate_local_store(self.location)
210
211        if ((self.remote_protocol not in verified_working_protocol) and (self.remote_protocol != 'ALL')):
212            self.logger.info("\n\tWorking protocol list: %s" % verified_working_protocol)
213            self.logger.error(
214                '\tERROR: Requested protocol %s is not in working protocol list.\n'
215                % self.remote_protocol)
216            sys.exit(-1)
217        else:
218            self.generate_ffdc(verified_working_protocol)
219
220    def ssh_to_target_system(self):
221        r"""
222        Open a ssh connection to targeted system.
223
224        """
225
226        self.ssh_remoteclient = SSHRemoteclient(self.hostname,
227                                                self.username,
228                                                self.password)
229
230        if self.ssh_remoteclient.ssh_remoteclient_login():
231            self.logger.info("\n\t[Check] %s SSH connection established.\t [OK]" % self.hostname)
232
233            # Check scp connection.
234            # If scp connection fails,
235            # continue with FFDC generation but skip scp files to local host.
236            self.ssh_remoteclient.scp_connection()
237            return True
238        else:
239            self.logger.info("\n\t[Check] %s SSH connection.\t [NOT AVAILABLE]" % self.hostname)
240            return False
241
242    def telnet_to_target_system(self):
243        r"""
244        Open a telnet connection to targeted system.
245        """
246        self.telnet_remoteclient = TelnetRemoteclient(self.hostname,
247                                                      self.username,
248                                                      self.password)
249        if self.telnet_remoteclient.tn_remoteclient_login():
250            self.logger.info("\n\t[Check] %s Telnet connection established.\t [OK]" % self.hostname)
251            return True
252        else:
253            self.logger.info("\n\t[Check] %s Telnet connection.\t [NOT AVAILABLE]" % self.hostname)
254            return False
255
256    def generate_ffdc(self, working_protocol_list):
257        r"""
258        Determine actions based on remote host type
259
260        Description of argument(s):
261        working_protocol_list    list of confirmed working protocols to connect to remote host.
262        """
263
264        self.logger.info("\n\t---- Executing commands on " + self.hostname + " ----")
265        self.logger.info("\n\tWorking protocol list: %s" % working_protocol_list)
266
267        config_dict = self.ffdc_actions
268        for target_type in config_dict.keys():
269            if self.target_type != target_type:
270                continue
271
272            self.logger.info("\n\tFFDC Path: %s " % self.ffdc_dir_path)
273            self.logger.info("\tSystem Type: %s" % target_type)
274            for k, v in config_dict[target_type].items():
275
276                if self.remote_protocol not in working_protocol_list \
277                        and self.remote_protocol != 'ALL':
278                    continue
279
280                protocol = config_dict[target_type][k]['PROTOCOL'][0]
281
282                if protocol not in working_protocol_list:
283                    continue
284
285                if protocol == 'SSH' or protocol == 'SCP':
286                    if 'SSH' in working_protocol_list or 'SCP' in working_protocol_list:
287                        self.protocol_ssh(target_type, k)
288                    else:
289                        self.logger.error("\n\tERROR: SSH or SCP is not available for %s." % self.hostname)
290
291                if protocol == 'TELNET':
292                    if protocol in working_protocol_list:
293                        self.protocol_telnet(target_type, k)
294                    else:
295                        self.logger.error("\n\tERROR: TELNET is not available for %s." % self.hostname)
296
297                if protocol == 'REDFISH':
298                    if protocol in working_protocol_list:
299                        self.protocol_redfish(target_type, k)
300                    else:
301                        self.logger.error("\n\tERROR: REDFISH is not available for %s." % self.hostname)
302
303                if protocol == 'IPMI':
304                    if protocol in working_protocol_list:
305                        self.protocol_ipmi(target_type, k)
306                    else:
307                        self.logger.error("\n\tERROR: IPMI is not available for %s." % self.hostname)
308
309                if protocol == 'SHELL':
310                    if protocol in working_protocol_list:
311                        self.protocol_shell_script(target_type, k)
312                    else:
313                        self.logger.error("\n\tERROR: can't execute SHELL script")
314
315        # Close network connection after collecting all files
316        self.elapsed_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - self.start_time))
317        if self.ssh_remoteclient:
318            self.ssh_remoteclient.ssh_remoteclient_disconnect()
319        if self.telnet_remoteclient:
320            self.telnet_remoteclient.tn_remoteclient_disconnect()
321
322    def protocol_ssh(self,
323                     target_type,
324                     sub_type):
325        r"""
326        Perform actions using SSH and SCP protocols.
327
328        Description of argument(s):
329        target_type         OS Type of remote host.
330        sub_type            Group type of commands.
331        """
332
333        if sub_type == 'DUMP_LOGS':
334            self.group_copy(self.ffdc_actions[target_type][sub_type])
335        else:
336            self.collect_and_copy_ffdc(self.ffdc_actions[target_type][sub_type])
337
338    def protocol_telnet(self,
339                        target_type,
340                        sub_type):
341        r"""
342        Perform actions using telnet protocol.
343        Description of argument(s):
344        target_type          OS Type of remote host.
345        """
346        self.logger.info("\n\t[Run] Executing commands on %s using %s" % (self.hostname, 'TELNET'))
347        telnet_files_saved = []
348        progress_counter = 0
349        list_of_commands = self.ffdc_actions[target_type][sub_type]['COMMANDS']
350        for index, each_cmd in enumerate(list_of_commands, start=0):
351            command_txt, command_timeout = self.unpack_command(each_cmd)
352            result = self.telnet_remoteclient.execute_command(command_txt, command_timeout)
353            if result:
354                try:
355                    targ_file = self.ffdc_actions[target_type][sub_type]['FILES'][index]
356                except IndexError:
357                    targ_file = command_txt
358                    self.logger.warning(
359                        "\n\t[WARN] Missing filename to store data from telnet %s." % each_cmd)
360                    self.logger.warning("\t[WARN] Data will be stored in %s." % targ_file)
361                targ_file_with_path = (self.ffdc_dir_path
362                                       + self.ffdc_prefix
363                                       + targ_file)
364                # Creates a new file
365                with open(targ_file_with_path, 'wb') as fp:
366                    fp.write(result)
367                    fp.close
368                    telnet_files_saved.append(targ_file)
369            progress_counter += 1
370            self.print_progress(progress_counter)
371        self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
372        for file in telnet_files_saved:
373            self.logger.info("\n\t\tSuccessfully save file " + file + ".")
374
375    def protocol_redfish(self,
376                         target_type,
377                         sub_type):
378        r"""
379        Perform actions using Redfish protocol.
380
381        Description of argument(s):
382        target_type         OS Type of remote host.
383        sub_type            Group type of commands.
384        """
385
386        self.logger.info("\n\t[Run] Executing commands to %s using %s" % (self.hostname, 'REDFISH'))
387        redfish_files_saved = []
388        progress_counter = 0
389        list_of_URL = self.ffdc_actions[target_type][sub_type]['URL']
390        for index, each_url in enumerate(list_of_URL, start=0):
391            redfish_parm = '-u ' + self.username + ' -p ' + self.password + ' -r ' \
392                           + self.hostname + ' -S Always raw GET ' + each_url
393
394            result = self.run_redfishtool(redfish_parm)
395            if result:
396                try:
397                    targ_file = self.get_file_list(self.ffdc_actions[target_type][sub_type])[index]
398                except IndexError:
399                    targ_file = each_url.split('/')[-1]
400                    self.logger.warning(
401                        "\n\t[WARN] Missing filename to store data from redfish URL %s." % each_url)
402                    self.logger.warning("\t[WARN] Data will be stored in %s." % targ_file)
403
404                targ_file_with_path = (self.ffdc_dir_path
405                                       + self.ffdc_prefix
406                                       + targ_file)
407
408                # Creates a new file
409                with open(targ_file_with_path, 'w') as fp:
410                    fp.write(result)
411                    fp.close
412                    redfish_files_saved.append(targ_file)
413
414            progress_counter += 1
415            self.print_progress(progress_counter)
416
417        self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
418
419        for file in redfish_files_saved:
420            self.logger.info("\n\t\tSuccessfully save file " + file + ".")
421
422    def protocol_ipmi(self,
423                      target_type,
424                      sub_type):
425        r"""
426        Perform actions using ipmitool over LAN protocol.
427
428        Description of argument(s):
429        target_type         OS Type of remote host.
430        sub_type            Group type of commands.
431        """
432
433        self.logger.info("\n\t[Run] Executing commands to %s using %s" % (self.hostname, 'IPMI'))
434        ipmi_files_saved = []
435        progress_counter = 0
436        list_of_cmd = self.get_command_list(self.ffdc_actions[target_type][sub_type])
437        for index, each_cmd in enumerate(list_of_cmd, start=0):
438            ipmi_parm = '-U ' + self.username + ' -P ' + self.password + ' -H ' \
439                + self.hostname + ' -I lanplus ' + each_cmd
440
441            result = self.run_ipmitool(ipmi_parm)
442            if result:
443                try:
444                    targ_file = self.get_file_list(self.ffdc_actions[target_type][sub_type])[index]
445                except IndexError:
446                    targ_file = each_cmd.split('/')[-1]
447                    self.logger.warning("\n\t[WARN] Missing filename to store data from IPMI %s." % each_cmd)
448                    self.logger.warning("\t[WARN] Data will be stored in %s." % targ_file)
449
450                targ_file_with_path = (self.ffdc_dir_path
451                                       + self.ffdc_prefix
452                                       + targ_file)
453
454                # Creates a new file
455                with open(targ_file_with_path, 'w') as fp:
456                    fp.write(result)
457                    fp.close
458                    ipmi_files_saved.append(targ_file)
459
460            progress_counter += 1
461            self.print_progress(progress_counter)
462
463        self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
464
465        for file in ipmi_files_saved:
466            self.logger.info("\n\t\tSuccessfully save file " + file + ".")
467
468    def collect_and_copy_ffdc(self,
469                              ffdc_actions_for_target_type,
470                              form_filename=False):
471        r"""
472        Send commands in ffdc_config file to targeted system.
473
474        Description of argument(s):
475        ffdc_actions_for_target_type     commands and files for the selected remote host type.
476        form_filename                    if true, pre-pend self.target_type to filename
477        """
478
479        # Executing commands, if any
480        self.ssh_execute_ffdc_commands(ffdc_actions_for_target_type,
481                                       form_filename)
482
483        # Copying files
484        if self.ssh_remoteclient.scpclient:
485            self.logger.info("\n\n\tCopying FFDC files from remote system %s.\n" % self.hostname)
486
487            # Retrieving files from target system
488            list_of_files = self.get_file_list(ffdc_actions_for_target_type)
489            self.scp_ffdc(self.ffdc_dir_path, self.ffdc_prefix, form_filename, list_of_files)
490        else:
491            self.logger.info("\n\n\tSkip copying FFDC files from remote system %s.\n" % self.hostname)
492
493    def get_command_list(self,
494                         ffdc_actions_for_target_type):
495        r"""
496        Fetch list of commands from configuration file
497
498        Description of argument(s):
499        ffdc_actions_for_target_type    commands and files for the selected remote host type.
500        """
501        try:
502            list_of_commands = ffdc_actions_for_target_type['COMMANDS']
503        except KeyError:
504            list_of_commands = []
505        return list_of_commands
506
507    def get_file_list(self,
508                      ffdc_actions_for_target_type):
509        r"""
510        Fetch list of commands from configuration file
511
512        Description of argument(s):
513        ffdc_actions_for_target_type    commands and files for the selected remote host type.
514        """
515        try:
516            list_of_files = ffdc_actions_for_target_type['FILES']
517        except KeyError:
518            list_of_files = []
519        return list_of_files
520
521    def unpack_command(self,
522                       command):
523        r"""
524        Unpack command from config file
525
526        Description of argument(s):
527        command    Command from config file.
528        """
529        if isinstance(command, dict):
530            command_txt = next(iter(command))
531            command_timeout = next(iter(command.values()))
532        elif isinstance(command, str):
533            command_txt = command
534            # Default command timeout 60 seconds
535            command_timeout = 60
536
537        return command_txt, command_timeout
538
539    def ssh_execute_ffdc_commands(self,
540                                  ffdc_actions_for_target_type,
541                                  form_filename=False):
542        r"""
543        Send commands in ffdc_config file to targeted system.
544
545        Description of argument(s):
546        ffdc_actions_for_target_type    commands and files for the selected remote host type.
547        form_filename                    if true, pre-pend self.target_type to filename
548        """
549        self.logger.info("\n\t[Run] Executing commands on %s using %s"
550                         % (self.hostname, ffdc_actions_for_target_type['PROTOCOL'][0]))
551
552        list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
553        # If command list is empty, returns
554        if not list_of_commands:
555            return
556
557        progress_counter = 0
558        for command in list_of_commands:
559            command_txt, command_timeout = self.unpack_command(command)
560
561            if form_filename:
562                command_txt = str(command_txt % self.target_type)
563
564            cmd_exit_code, err, response = \
565                self.ssh_remoteclient.execute_command(command_txt, command_timeout)
566
567            if cmd_exit_code:
568                self.logger.warning(
569                    "\n\t\t[WARN] %s exits with code %s." % (command_txt, str(cmd_exit_code)))
570                self.logger.warning("\t\t[WARN] %s " % err)
571
572            progress_counter += 1
573            self.print_progress(progress_counter)
574
575        self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
576
577    def group_copy(self,
578                   ffdc_actions_for_target_type):
579        r"""
580        scp group of files (wild card) from remote host.
581
582        Description of argument(s):
583        fdc_actions_for_target_type    commands and files for the selected remote host type.
584        """
585
586        if self.ssh_remoteclient.scpclient:
587            self.logger.info("\n\tCopying DUMP files from remote system %s.\n" % self.hostname)
588
589            list_of_commands = self.get_command_list(ffdc_actions_for_target_type)
590            # If command list is empty, returns
591            if not list_of_commands:
592                return
593
594            for command in list_of_commands:
595                try:
596                    filename = command.split(' ')[2]
597                except IndexError:
598                    self.logger.info("\t\tInvalid command %s for DUMP_LOGS block." % command)
599                    continue
600
601                cmd_exit_code, err, response = \
602                    self.ssh_remoteclient.execute_command(command)
603
604                # If file does not exist, code take no action.
605                # cmd_exit_code is ignored for this scenario.
606                if response:
607                    scp_result = self.ssh_remoteclient.scp_file_from_remote(filename, self.ffdc_dir_path)
608                    if scp_result:
609                        self.logger.info("\t\tSuccessfully copied from " + self.hostname + ':' + filename)
610                else:
611                    self.logger.info("\t\tThere is no " + filename)
612
613        else:
614            self.logger.info("\n\n\tSkip copying files from remote system %s.\n" % self.hostname)
615
616    def scp_ffdc(self,
617                 targ_dir_path,
618                 targ_file_prefix,
619                 form_filename,
620                 file_list=None,
621                 quiet=None):
622        r"""
623        SCP all files in file_dict to the indicated directory on the local system.
624
625        Description of argument(s):
626        targ_dir_path                   The path of the directory to receive the files.
627        targ_file_prefix                Prefix which will be pre-pended to each
628                                        target file's name.
629        file_dict                       A dictionary of files to scp from targeted system to this system
630
631        """
632
633        progress_counter = 0
634        for filename in file_list:
635            if form_filename:
636                filename = str(filename % self.target_type)
637            source_file_path = filename
638            targ_file_path = targ_dir_path + targ_file_prefix + filename.split('/')[-1]
639
640            # If source file name contains wild card, copy filename as is.
641            if '*' in source_file_path:
642                scp_result = self.ssh_remoteclient.scp_file_from_remote(source_file_path, self.ffdc_dir_path)
643            else:
644                scp_result = self.ssh_remoteclient.scp_file_from_remote(source_file_path, targ_file_path)
645
646            if not quiet:
647                if scp_result:
648                    self.logger.info(
649                        "\t\tSuccessfully copied from " + self.hostname + ':' + source_file_path + ".\n")
650                else:
651                    self.logger.info(
652                        "\t\tFail to copy from " + self.hostname + ':' + source_file_path + ".\n")
653            else:
654                progress_counter += 1
655                self.print_progress(progress_counter)
656
657    def set_ffdc_defaults(self):
658        r"""
659        Set a default value for self.ffdc_dir_path and self.ffdc_prefix.
660        Collected ffdc file will be stored in dir /self.location/hostname_timestr/.
661        Individual ffdc file will have timestr_filename.
662
663        Description of class variables:
664        self.ffdc_dir_path  The dir path where collected ffdc data files should be put.
665
666        self.ffdc_prefix    The prefix to be given to each ffdc file name.
667
668        """
669
670        timestr = time.strftime("%Y%m%d-%H%M%S")
671        self.ffdc_dir_path = self.location + "/" + self.hostname + "_" + timestr + "/"
672        self.ffdc_prefix = timestr + "_"
673        self.validate_local_store(self.ffdc_dir_path)
674
675    def validate_local_store(self, dir_path):
676        r"""
677        Ensure path exists to store FFDC files locally.
678
679        Description of variable:
680        dir_path  The dir path where collected ffdc data files will be stored.
681
682        """
683
684        if not os.path.exists(dir_path):
685            try:
686                os.makedirs(dir_path, 0o755)
687            except (IOError, OSError) as e:
688                # PermissionError
689                if e.errno == EPERM or e.errno == EACCES:
690                    self.logger.error(
691                        '\tERROR: os.makedirs %s failed with PermissionError.\n' % dir_path)
692                else:
693                    self.logger.error(
694                        '\tERROR: os.makedirs %s failed with %s.\n' % (dir_path, e.strerror))
695                sys.exit(-1)
696
697    def print_progress(self, progress):
698        r"""
699        Print activity progress +
700
701        Description of variable:
702        progress  Progress counter.
703
704        """
705
706        sys.stdout.write("\r\t" + "+" * progress)
707        sys.stdout.flush()
708        time.sleep(.1)
709
710    def verify_redfish(self):
711        r"""
712        Verify remote host has redfish service active
713
714        """
715        redfish_parm = '-u ' + self.username + ' -p ' + self.password + ' -r ' \
716                       + self.hostname + ' -S Always raw GET /redfish/v1/'
717        return(self.run_redfishtool(redfish_parm, True))
718
719    def verify_ipmi(self):
720        r"""
721        Verify remote host has IPMI LAN service active
722
723        """
724        ipmi_parm = '-U ' + self.username + ' -P ' + self.password + ' -H ' \
725            + self.hostname + ' power status -I lanplus'
726        return(self.run_ipmitool(ipmi_parm, True))
727
728    def run_redfishtool(self,
729                        parms_string,
730                        quiet=False):
731        r"""
732        Run CLI redfishtool
733
734        Description of variable:
735        parms_string         redfishtool subcommand and options.
736        quiet                do not print redfishtool error message if True
737        """
738
739        result = subprocess.run(['redfishtool ' + parms_string],
740                                stdout=subprocess.PIPE,
741                                stderr=subprocess.PIPE,
742                                shell=True,
743                                universal_newlines=True)
744
745        if result.stderr and not quiet:
746            self.logger.error('\n\tERROR with redfishtool ' + parms_string)
747            self.logger.error('\n\t%s' % result.stderr.split('\n'))
748
749        return result.stdout
750
751    def run_ipmitool(self,
752                     parms_string,
753                     quiet=False):
754        r"""
755        Run CLI IPMI tool.
756
757        Description of variable:
758        parms_string         ipmitool subcommand and options.
759        quiet                do not print redfishtool error message if True
760        """
761
762        result = subprocess.run(['ipmitool -I lanplus -C 17 ' + parms_string],
763                                stdout=subprocess.PIPE,
764                                stderr=subprocess.PIPE,
765                                shell=True,
766                                universal_newlines=True)
767
768        if result.stderr and not quiet:
769            self.logger.error('\n\t\tERROR with ipmitool -I lanplus -C 17 ' + parms_string)
770            self.logger.error('\t\t' + result.stderr)
771
772        return result.stdout
773
774    def run_shell_script(self,
775                         parms_string,
776                         quiet=False):
777        r"""
778        Run CLI shell script tool.
779
780        Description of variable:
781        parms_string         script command options.
782        quiet                do not print redfishtool error message if True
783        """
784
785        result = subprocess.run([parms_string],
786                                stdout=subprocess.PIPE,
787                                stderr=subprocess.PIPE,
788                                shell=True,
789                                universal_newlines=True)
790
791        if result.stderr and not quiet:
792            self.logger.error('\n\t\tERROR executing %s' % parms_string)
793            self.logger.error('\t\t' + result.stderr)
794
795        return result.stdout
796
797    def protocol_shell_script(self,
798                              target_type,
799                              sub_type):
800        r"""
801        Perform SHELL script execution locally.
802
803        Description of argument(s):
804        target_type         OS Type of remote host.
805        sub_type            Group type of commands.
806        """
807
808        self.logger.info("\n\t[Run] Executing commands to %s using %s" % (self.hostname, 'SHELL'))
809        shell_files_saved = []
810        progress_counter = 0
811        list_of_cmd = self.get_command_list(self.ffdc_actions[target_type][sub_type])
812        for index, each_cmd in enumerate(list_of_cmd, start=0):
813
814            result = self.run_shell_script(each_cmd)
815            if result:
816                try:
817                    targ_file = self.get_file_list(self.ffdc_actions[target_type][sub_type])[index]
818                except IndexError:
819                    targ_file = each_cmd.split('/')[-1]
820                    self.logger.warning("\n\t[WARN] Missing filename to store data %s." % each_cmd)
821                    self.logger.warning("\t[WARN] Data will be stored in %s." % targ_file)
822
823                targ_file_with_path = (self.ffdc_dir_path
824                                       + self.ffdc_prefix
825                                       + targ_file)
826
827                # Creates a new file
828                with open(targ_file_with_path, 'w') as fp:
829                    fp.write(result)
830                    fp.close
831                    shell_files_saved.append(targ_file)
832
833            progress_counter += 1
834            self.print_progress(progress_counter)
835
836        self.logger.info("\n\t[Run] Commands execution completed.\t\t [OK]")
837
838        for file in shell_files_saved:
839            self.logger.info("\n\t\tSuccessfully save file " + file + ".")
840
841    def verify_protocol(self, protocol_list):
842        r"""
843        Perform protocol working check.
844
845        Description of argument(s):
846        protocol_list        List of protocol.
847        """
848
849        tmp_list = []
850        if self.target_is_pingable():
851            tmp_list.append("SHELL")
852
853        for protocol in protocol_list:
854            if self.remote_protocol != 'ALL':
855                if self.remote_protocol != protocol:
856                    continue
857
858            # Only check SSH/SCP once for both protocols
859            if protocol == 'SSH' or protocol == 'SCP' and protocol not in tmp_list:
860                if self.ssh_to_target_system():
861                    tmp_list.append('SSH')
862                    tmp_list.append('SCP')
863
864            if protocol == 'TELNET':
865                if self.telnet_to_target_system():
866                    tmp_list.append(protocol)
867
868            if protocol == 'REDFISH':
869                if self.verify_redfish():
870                    tmp_list.append(protocol)
871                    self.logger.info("\n\t[Check] %s Redfish Service.\t\t [OK]" % self.hostname)
872                else:
873                    self.logger.info("\n\t[Check] %s Redfish Service.\t\t [NOT AVAILABLE]" % self.hostname)
874
875            if protocol == 'IPMI':
876                if self.verify_ipmi():
877                    tmp_list.append(protocol)
878                    self.logger.info("\n\t[Check] %s IPMI LAN Service.\t\t [OK]" % self.hostname)
879                else:
880                    self.logger.info("\n\t[Check] %s IPMI LAN Service.\t\t [NOT AVAILABLE]" % self.hostname)
881
882        return tmp_list
883