1#!/usr/bin/env python
2
3r"""
4See class prolog below for details.
5"""
6
7import os
8import sys
9import yaml
10import time
11import platform
12from errno import EACCES, EPERM
13from ssh_utility import SSHRemoteclient
14
15
16class FFDCCollector:
17
18    r"""
19    Sends commands from configuration file to the targeted system to collect log files.
20    Fetch and store generated files at the specified location.
21
22    """
23
24    def __init__(self, hostname, username, password, ffdc_config, location):
25
26        r"""
27        Description of argument(s):
28
29        hostname                name/ip of the targeted (remote) system
30        username                user on the targeted system with access to FFDC files
31        password                password for user on targeted system
32        ffdc_config             configuration file listing commands and files for FFDC
33        location                Where to store collected FFDC
34
35        """
36        if self.verify_script_env():
37            self.hostname = hostname
38            self.username = username
39            self.password = password
40            self.ffdc_config = ffdc_config
41            self.location = location
42            self.remote_client = None
43            self.ffdc_dir_path = ""
44            self.ffdc_prefix = ""
45            self.receive_file_list = []
46            self.target_type = ""
47        else:
48            sys.exit(-1)
49
50    def verify_script_env(self):
51
52        # Import to log version
53        import click
54        import paramiko
55
56        run_env_ok = True
57        print("\n\t---- Script host environment ----")
58        print("\t{:<10}  {:<10}".format('Script hostname', os.uname()[1]))
59        print("\t{:<10}  {:<10}".format('Script host os', platform.platform()))
60        print("\t{:<10}  {:>10}".format('Python', platform.python_version()))
61        print("\t{:<10}  {:>10}".format('PyYAML', yaml.__version__))
62        print("\t{:<10}  {:>10}".format('click', click.__version__))
63        print("\t{:<10}  {:>10}".format('paramiko', paramiko.__version__))
64
65        if eval(yaml.__version__.replace('.', ',')) < (5, 4, 1):
66            print("\n\tERROR: Python or python packages do not meet minimum version requirement.")
67            print("\tERROR: PyYAML version 5.4.1 or higher is needed.\n")
68            run_env_ok = False
69
70        print("\t---- End script host environment ----")
71        return run_env_ok
72
73    def target_is_pingable(self):
74
75        r"""
76        Check if target system is ping-able.
77
78        """
79        response = os.system("ping -c 1 -w 2 %s  2>&1 >/dev/null" % self.hostname)
80        if response == 0:
81            print("\n\t%s is ping-able." % self.hostname)
82            return True
83        else:
84            print("\n>>>>>\tERROR: %s is not ping-able. FFDC collection aborted.\n" % self.hostname)
85            sys.exit(-1)
86
87    def set_target_machine_type(self):
88        r"""
89        Determine and set target machine type.
90
91        """
92        # Default to openbmc for first few sprints
93        self.target_type = "OPENBMC"
94
95    def collect_ffdc(self):
96
97        r"""
98        Initiate FFDC Collection depending on requested protocol.
99
100        """
101
102        print("\n\n\t---- Start communicating with %s ----\n" % self.hostname)
103        if self.target_is_pingable():
104            # Verify top level directory exists for storage
105            self.validate_local_store(self.location)
106            self.set_target_machine_type()
107            self.generate_ffdc()
108
109    def ssh_to_target_system(self):
110        r"""
111        Open a ssh connection to targeted system.
112
113        """
114
115        self.remoteclient = SSHRemoteclient(self.hostname,
116                                            self.username,
117                                            self.password)
118
119        self.remoteclient.ssh_remoteclient_login()
120
121    def generate_ffdc(self):
122
123        r"""
124        Send commands in ffdc_config file to targeted system.
125
126        """
127
128        with open(self.ffdc_config, 'r') as file:
129            ffdc_actions = yaml.load(file, Loader=yaml.FullLoader)
130
131        for machine_type in ffdc_actions.keys():
132            if machine_type == self.target_type:
133
134                if (ffdc_actions[machine_type]['PROTOCOL'][0] == 'SSH'):
135
136                    # Use SSH
137                    self.ssh_to_target_system()
138
139                    print("\n\tCollecting FFDC on " + self.hostname)
140                    list_of_commands = ffdc_actions[machine_type]['COMMANDS']
141                    progress_counter = 0
142                    for command in list_of_commands:
143                        self.remoteclient.execute_command(command)
144                        progress_counter += 1
145                        self.print_progress(progress_counter)
146
147                    print("\n\n\tCopying FFDC from remote system %s \n\n" % self.hostname)
148                    # Get default values for scp action.
149                    # self.location == local system for now
150                    self.set_ffdc_defaults()
151                    # Retrieving files from target system
152                    list_of_files = ffdc_actions[machine_type]['FILES']
153                    self.scp_ffdc(self.ffdc_dir_path, self.ffdc_prefix, list_of_files)
154                else:
155                    print("\n\tProtocol %s is not yet supported by this script.\n"
156                          % ffdc_actions[machine_type]['PROTOCOL'][0])
157
158    def scp_ffdc(self,
159                 targ_dir_path,
160                 targ_file_prefix="",
161                 file_list=None,
162                 quiet=None):
163
164        r"""
165        SCP all files in file_dict to the indicated directory on the local system.
166
167        Description of argument(s):
168        targ_dir_path                   The path of the directory to receive the files.
169        targ_file_prefix                Prefix which will be pre-pended to each
170                                        target file's name.
171        file_dict                       A dictionary of files to scp from targeted system to this system
172
173        """
174
175        self.remoteclient.scp_connection()
176
177        self.receive_file_list = []
178        progress_counter = 0
179        for filename in file_list:
180            source_file_path = filename
181            targ_file_path = targ_dir_path + targ_file_prefix + filename.split('/')[-1]
182
183            # self.remoteclient.scp_file_from_remote() completed without exception,
184            # add file to the receiving file list.
185            scp_result = self.remoteclient.scp_file_from_remote(source_file_path, targ_file_path)
186            if scp_result:
187                self.receive_file_list.append(targ_file_path)
188
189            if not quiet:
190                if scp_result:
191                    print("\t\tSuccessfully copied from " + self.hostname + ':' + source_file_path + ".\n")
192                else:
193                    print("\t\tFail to copy from " + self.hostname + ':' + source_file_path + ".\n")
194            else:
195                progress_counter += 1
196                self.print_progress(progress_counter)
197
198        self.remoteclient.ssh_remoteclient_disconnect()
199
200    def set_ffdc_defaults(self):
201
202        r"""
203        Set a default value for self.ffdc_dir_path and self.ffdc_prefix.
204        Collected ffdc file will be stored in dir /self.location/hostname_timestr/.
205        Individual ffdc file will have timestr_filename.
206
207        Description of class variables:
208        self.ffdc_dir_path  The dir path where collected ffdc data files should be put.
209
210        self.ffdc_prefix    The prefix to be given to each ffdc file name.
211
212        """
213
214        timestr = time.strftime("%Y%m%d-%H%M%S")
215        self.ffdc_dir_path = self.location + "/" + self.hostname + "_" + timestr + "/"
216        self.ffdc_prefix = timestr + "_"
217        self.validate_local_store(self.ffdc_dir_path)
218
219    def validate_local_store(self, dir_path):
220        r"""
221        Ensure path exists to store FFDC files locally.
222
223        Description of variable:
224        dir_path  The dir path where collected ffdc data files will be stored.
225
226        """
227
228        if not os.path.exists(dir_path):
229            try:
230                os.mkdir(dir_path, 0o755)
231            except (IOError, OSError) as e:
232                # PermissionError
233                if e.errno == EPERM or e.errno == EACCES:
234                    print('>>>>>\tERROR: os.mkdir %s failed with PermissionError.\n' % dir_path)
235                else:
236                    print('>>>>>\tERROR: os.mkdir %s failed with %s.\n' % (dir_path, e.strerror))
237                sys.exit(-1)
238
239    def print_progress(self, progress):
240        r"""
241        Print activity progress +
242
243        Description of variable:
244        progress  Progress counter.
245
246        """
247
248        sys.stdout.write("\r\t" + "+" * progress)
249        sys.stdout.flush()
250        time.sleep(.1)
251