#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c): 2012-2017, Huawei Tech. Co., Ltd.
# Description  : SshTool.py is utility to support ssh tools
#############################################################################
try:
    import subprocess
    import os
    import sys
    import datetime
    import socket
    import weakref
    import stat
    from random import sample

    sys.path.append(sys.path[0] + "/../../")
    from gspylib.common.ErrorCode import ErrorCode
    from gspylib.common.Common import DefaultValue
    from gspylib.os.gsfile import g_file
except ImportError as ie:
    sys.exit("[GAUSS-52200] : Unable to import module: %s." % str(ie))

PSSH_PARALLEL_NUM = 32


class SshTool():
    """
    Class for controlling multi-hosts
    """

    def __init__(self, hostNames, key, logFile=None, timeout=0):
        '''
        Constructor
        '''
        self.hostNames = self.flush_hostnames(hostNames)
        self.__basePath = "/tmp"
        self.__logFile = logFile
        self.__pid = os.getpid()
        self.__timeout = timeout
        self._finalizer = weakref.finalize(self, self.clenSshResultFiles)
        self.parallNum = self.getParallNum(key)
        self.key = key

        currentTime = str(datetime.datetime.now()).replace(" ", "_").replace(".", "_")
        randomnum = ''.join(sample('0123456789', 3))
        # The tmp directory is a temporary folder shared by all users,
        # and all users have read and write permissions
        if key == "":
            fileFlag = "%d_%s_%s" % (self.__pid, currentTime, randomnum)
        else:
            fileFlag = "%d_%s_%s_%s" % (self.__pid, key, currentTime, randomnum)
        self.__hostsFile = os.path.join(self.__basePath, "gauss_hosts_file_%s" % fileFlag)
        self.__resultFile = os.path.join(self.__basePath, "gauss_result_%s.log" % fileFlag)
        self.__outputPath = os.path.join(self.__basePath, "gauss_output_files_%s" % fileFlag)
        self.__errorPath = os.path.join(self.__basePath, "gauss_error_files_%s" % fileFlag)
        self.__resultStatus = {}
        if logFile is None:
            self.__logFile = "/dev/null"

        # before using, clean the old ones
        g_file.removeFile(self.__hostsFile)
        g_file.removeFile(self.__resultFile)
        if os.path.exists(self.__outputPath):
            g_file.removeDirectory(self.__outputPath)
        if os.path.exists(self.__errorPath):
            g_file.removeDirectory(self.__errorPath)

        self.__writeHostFiles()

    def clenSshResultFiles(self):
        """
        function: Delete file
        input : NA
        output: NA
        """
        if os.path.exists(self.__hostsFile):
            g_file.removeFile(self.__hostsFile)

        if os.path.exists(self.__resultFile):
            g_file.removeFile(self.__resultFile)

        if os.path.exists(self.__outputPath):
            g_file.removeDirectory(self.__outputPath)

        if os.path.exists(self.__errorPath):
            g_file.removeDirectory(self.__errorPath)

    def getParallNum(self, key):
        """
        If it is a CPU-intensive application, the thread pool size is set to N+1
        If it is an IO-intensive application, the thread pool size is set to 2N+1
        """
        if key in DefaultValue.IO_INTENSIVE_ACTION_DICT.keys():
            return 2 * DefaultValue.getCpuSet() + 1
        else:
            return DefaultValue.getCpuSet() + 1

    def flush_hostnames(self, hostnames):
        """
        The local node is the last one to execute
        """
        local_host = socket.gethostname()
        if len(hostnames) > 0 and local_host in hostnames:
            hostnames.remove(local_host)
            hostnames.append(local_host)
        return hostnames

    def __del__(self):
        """
        function: Delete file
        input : NA
        output: NA
        """
        self._finalizer()

    def exchangeHostnameSshKeys(self, user, pwd, mpprcFile=""):
        """
        function: Exchange ssh public keys for specified user, using hostname
        input : user, pwd, mpprcFile
        output: NA
        """
        if mpprcFile != "":
            exkeyCmd = "su - %s -c 'source %s&&mvxssh-exkeys -f %s -p %s' 2>>%s" % \
                       (user, mpprcFile, self.__hostsFile, pwd, self.__logFile)
        else:
            exkeyCmd = "su - %s -c 'source /etc/profile&&mvxssh-exkeys -f %s -p %s' 2>>%s" % \
                       (user, self.__hostsFile, pwd, self.__logFile)
        (status, output) = subprocess.getstatusoutput(exkeyCmd)
        if status != 0:
            raise Exception(ErrorCode.GAUSS_511["GAUSS_51112"] %
                            user + "Error: \n%s" % output.replace(pwd, "******") +
                            "\nYou can comment Cipher 3des. Ciphers aes128-cbc "
                            "and MACs in /etc/ssh/ssh_config and try again.")

    def exchangeIpSshKeys(self, user, pwd, ips, mpprcFile=""):
        """
        function: Exchange ssh public keys for specified user, using ip address
        input : user, pwd, ips, mpprcFile
        output: NA
        """
        if mpprcFile != "":
            cmd_list = ["su - %s -c 'source %s&&mvxssh-exkeys " % (user, mpprcFile)]
        else:
            cmd_list = ["su - %s -c 'source /etc/profile&&mvxssh-exkeys " % user]
        for ip in ips:
            cmd_list.append(" -h %s " % ip.strip())
        ex_key_cmd = "".join(cmd_list)
        ex_key_cmd += "-p %s' 2>>%s" % (pwd, self.__logFile)
        (status, output) = subprocess.getstatusoutput(ex_key_cmd)
        if status != 0:
            raise Exception(ErrorCode.GAUSS_511["GAUSS_51112"] %
                            user + "Error: \n%s" % output.replace(pwd, "******") +
                            "\nYou can comment Cipher 3des, Ciphers aes128-cbc and "
                            "MACs in /etc/ssh/ssh_config and try again.")

    def createTrust(self, user, pwd, ips=None, mpprcFile="", skipHostnameSet=False,
                    preMode=False, logUuid="", logAction="", logStep=""):
        """
        function: create trust for specified user with both ip and hostname, when using N9000 tool create trust failed
                  do not support using a normal user to create trust for another user.
        input : user, pwd, ips, mpprcFile, skipHostnameSet
        output: NA
        """
        tmp_hosts = "/tmp/tmp_hosts_%d" % self.__pid
        if ips is None:
            ips = []
        try:
            g_file.removeFile(tmp_hosts)
            # 1.prepare hosts file
            for ip in ips:
                cmd = "echo %s >> %s 2>/dev/null" % (ip, tmp_hosts)
                (status, output) = subprocess.getstatusoutput(cmd)
                if status != 0:
                    raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] % tmp_hosts + " Error:\n%s" % output)
            cmd = "chmod %s '%s'" % (DefaultValue.HOSTS_FILE, tmp_hosts)
            (status, output) = subprocess.getstatusoutput(cmd)
            if status != 0:
                raise Exception(ErrorCode.GAUSS_501["GAUSS_50107"] % tmp_hosts + " Error:\n%s" % output)

            # 2.call create trust script
            cmd = self.get_create_trust_cmd(user, mpprcFile, pwd, tmp_hosts)

            if skipHostnameSet:
                cmd += " --skip-hostname-set"
            cmd += " --uuid=%s --logAction=%s --logStep=%s" % (logUuid, logAction, logStep)
            if os.getuid() == 0:
                cmd = "su - %s -c \"%s\" 2>&1" % (user, cmd)
            else:
                cmd += " 2>&1"

            status = os.system(cmd)

            if status != 0:
                # we can not print cmd here, because it include user's passwd
                g_file.removeFile(tmp_hosts)
                sys.exit(1)

            # 3.delete hosts file
            g_file.removeFile(tmp_hosts)
        except Exception as e:
            g_file.removeFile(tmp_hosts)
            raise Exception(str(e))

    def get_create_trust_cmd(self, user, mpprcFile, pwd, tmp_hosts):
        """
        """
        cmd = ""
        create_trust_file = "gs_sshexkey"
        if user == "root":
            if (mpprcFile != "" and g_file.checkFilePermission(mpprcFile, True) and
                    self.checkMpprcfile(user, mpprcFile)):
                cmd = "source %s; echo '%s' | %s -f %s -l '%s'" % \
                      (mpprcFile, pwd, create_trust_file, tmp_hosts, self.__logFile)
            elif mpprcFile == "" and g_file.checkFilePermission('/etc/profile', True):
                cmd = "source /etc/profile; echo '%s' | %s -f %s -l '%s'" % \
                      (pwd, create_trust_file, tmp_hosts, self.__logFile)
        else:
            if (mpprcFile != "" and g_file.checkFilePermission(mpprcFile, True) and
                    self.checkMpprcfile(user, mpprcFile)):
                cmd = "source %s; %s -f %s -l '%s'" % \
                      (mpprcFile, create_trust_file, tmp_hosts, self.__logFile)
            elif mpprcFile == "" and g_file.checkFilePermission('/etc/profile', True):
                cmd = "source /etc/profile; %s -f %s -l '%s'" % \
                      (create_trust_file, tmp_hosts, self.__logFile)
        return cmd

    def checkMpprcfile(self, username, filePath):
        """
        function:
          check if given user has operation permission for Mpprcfile
        precondition:
          1.user should be exist---root/cluster user
          2.filePath should be an absolute path
        postcondition:
          1.return True or False
        input : username,filePath
        output: True/False
        """
        (ownerPath, _) = os.path.split(filePath)
        cmd = "su - %s -c 'cd %s'" % (username, ownerPath)
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0:
            raise Exception(ErrorCode.GAUSS_500["GAUSS_50004"] % '-sep-env-file' + " Error:\n%s" % output)

        return True

    def getUserOSProfile(self, env_file=""):
        """
        function: get user os profile
        input : env_file
        output: mpprcFile, userProfile, osProfile
        """
        if env_file != "":
            mpprcFile = env_file
        else:
            mpprcFile = DefaultValue.getEnv(DefaultValue.MPPRC_FILE_ENV)

        if mpprcFile != "" and mpprcFile is not None:
            userProfile = mpprcFile
            osProfile = mpprcFile
        else:
            userProfile = "~/.bashrc"
            osProfile = "/etc/profile"
        return mpprcFile, userProfile, osProfile

    def getGPHOMEPath(self, osProfile):
        """
        function: get GPHOME path
        input : osProfile
        output: output
        """
        try:
            cmd = "source %s && echo $GPHOME" % osProfile
            (status, output) = subprocess.getstatusoutput(cmd)
            if status != 0 or output is None or output.strip() == "":
                raise Exception(ErrorCode.GAUSS_518["GAUSS_51802"] % "GPHOME")
            return output.strip()
        except Exception as e:
            raise Exception(str(e))

    def parseSshResult(self, cmd, hostList=None, output_info=None):
        """
        function: parse ssh result
        input : cmd, hostList
        output: resultMap, outputCollect
        """
        if hostList is None:
            hostList = []
        try:
            outputCollect = ""
            item = list()
            resultMap = self.__readCmdResult(self.__resultFile, len(hostList), cmd, output_info)
            for host in hostList:
                sshOutPutFile = "%s/%s" % (self.__outputPath, host)
                sshErrorPutFile = "%s/%s" % (self.__errorPath, host)
                if resultMap[host] == DefaultValue.SUCCESS:
                    prefix = "SUCCESS"
                else:
                    prefix = "FAILURE"
                item.append("[%s] %s:\n" % (prefix, str(host)))
                outputCollect = "".join(item)
                if os.path.isfile(sshOutPutFile):
                    with open(sshOutPutFile, "r") as fp:
                        context = fp.read()
                    item.append(context)
                    outputCollect = "".join(item)
                if os.path.isfile(sshErrorPutFile):
                    with open(sshErrorPutFile, "r") as fp:
                        context = fp.read()
                    item.append(context)
                    outputCollect = "".join(item)
        except Exception as e:
            raise Exception(str(e))
        return resultMap, outputCollect

    def timeOutClean(self, cmd, psshpre, hostList=None, env_file="", signal=9):
        """
        """
        if hostList is None:
            hostList = []
        mpprcFile, userProfile, osProfile = self.getUserOSProfile(env_file)
        # kill the parent and child process. get all process by pstree
        timeOutCmd = "pidList=\`ps aux | grep \\\"%s\\\" | grep -v 'grep' | awk '{print \$2}' | xargs \`; " % cmd
        timeOutCmd += "for pid in \$pidList; do pstree -alnp \$pid | awk -F ',' '{print \$2}' | " \
                      "awk '{print \$1}' | xargs -r -n 100 kill -%s; done" % \
                      str(signal)
        if len(hostList) == 0:
            sshCmd = self.get_ssh_cmd_without_host_list(osProfile, mpprcFile, userProfile,
                                                        psshpre, self.parallNum, timeOutCmd)
        else:
            sshCmd = self.get_ssh_cmd_with_host_list(osProfile, mpprcFile, userProfile,
                                                     psshpre, self.parallNum, timeOutCmd, hostList)
        (status, output) = subprocess.getstatusoutput(sshCmd)
        if status != 0:
            raise Exception("Error:%s" % output)

    def executeCommand(self, cmd, descript, cmdReturn=DefaultValue.SUCCESS, hostList=None, env_file="",
                       checkenv=False):
        """
        function: Execute command on all hosts
        input : cmd, descript, cmdReturn, hostList, env_file, checkenv
        output: NA
        """
        if hostList is None:
            hostList = []
        local_mode = False
        result_map = {}
        is_time_out = False
        parallel_num = self.parallNum
        try:
            mpprc_file, user_profile, os_profile = self.getUserOSProfile(env_file)
            gp_home = self.getGPHOMEPath(os_profile)
            pssh = "python3 %s/script/gspylib/pssh/bin/pssh" % gp_home

            # clean result file
            if os.path.exists(self.__resultFile):
                os.remove(self.__resultFile)

            if len(hostList) == 0:
                pssh_cmd = self.get_ssh_cmd_without_host_list(os_profile, mpprc_file, user_profile,
                                                              pssh, parallel_num, cmd)
                hostList = self.hostNames
            else:
                pssh_cmd = self.get_ssh_cmd_with_host_list(os_profile, mpprc_file, user_profile,
                                                           pssh, parallel_num, cmd, hostList)

            # single cluster or execute only in local node.
            if len(hostList) == 1 and hostList[0] == socket.gethostname() and cmd.find(" --lock-cluster ") < 0:
                local_mode = True
                if os.getuid() == 0 and (mpprc_file == "" or mpprc_file is None):
                    pssh_cmd = "source %s ; %s 2>&1" % (os_profile, cmd)
                else:
                    pssh_cmd = "source %s ; source %s; %s 2>&1" % (os_profile, user_profile, cmd)

            # if it is local_mode, it means does not call pssh, so there is no time out
            (status, output) = subprocess.getstatusoutput(pssh_cmd)
            # when the pssh is time out, kill parent and child process
            if not local_mode:
                if output.find("Timed out, Killed by signal 9") > 0:
                    self.timeOutClean(cmd, pssh, hostList, env_file)
                    is_time_out = True
                    raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % pssh_cmd + " Error:\n%s" % output)
                if status != 0:
                    raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % pssh_cmd + " Error:\n%s" % output)

            if local_mode:
                result_map[hostList[0]] = DefaultValue.SUCCESS if status == 0 else DefaultValue.FAILURE
                output_collect = "[%s] %s:\n%s" % ("SUCCESS" if status == 0 else "FAILURE", hostList[0], output)
            else:
                # ip and host name should match here
                result_map, output_collect = self.parseSshResult(cmd, hostList, output)
        except Exception as ex:
            if not is_time_out:
                self.clenSshResultFiles()
            raise Exception(str(ex))
        SshTool.check_ssh_result(cmd, hostList, result_map, cmdReturn, output_collect, output, mpprc_file, checkenv)

    def get_ssh_cmd_without_host_list(self, osProfile, mpprcFile, userProfile, psshpre, parallel_num, cmd):
        """
        """
        sshCmd = "source %s ; %s -t %s -h %s -P -p %s -o %s -e %s " \
                 "\"source %s;" % (osProfile,
                                   psshpre,
                                   self.__timeout,
                                   self.__hostsFile,
                                   parallel_num,
                                   self.__outputPath,
                                   self.__errorPath,
                                   osProfile)
        chmod_cmd = f"chmod {DefaultValue.FILE_MODE} '{self.__resultFile}'"
        if os.getuid() == 0 and (mpprcFile == "" or mpprcFile is None):
            sshCmd += " %s\" 2>&1 | tee %s && %s" % (cmd, self.__resultFile, chmod_cmd)
        else:
            sshCmd += "source %s;%s\" 2>&1 | tee %s && %s" % (userProfile, cmd, self.__resultFile, chmod_cmd)
        return sshCmd

    def get_ssh_cmd_with_host_list(self, osProfile, mpprcFile, userProfile, psshpre, parallel_num, cmd, hostList):
        """
        """
        sshCmd = "source %s ; %s -t %s -H %s -P -p %s -o %s -e %s " \
                 "\"source %s; " % (osProfile,
                                    psshpre,
                                    self.__timeout,
                                    " -H ".join(hostList),
                                    parallel_num,
                                    self.__outputPath,
                                    self.__errorPath,
                                    osProfile)
        chmod_cmd = f"chmod {DefaultValue.FILE_MODE} '{self.__resultFile}'"
        if os.getuid() == 0 and (mpprcFile == "" or mpprcFile is None):
            sshCmd += "%s\" 2>&1 | tee %s && %s" % (cmd, self.__resultFile, chmod_cmd)
        else:
            sshCmd += "source %s;%s\" 2>&1 | tee %s && %s" % (userProfile, cmd, self.__resultFile, chmod_cmd)

        return sshCmd

    @staticmethod
    def check_ssh_result(cmd, hostList, resultMap, cmdReturn, outputCollect, output, mpprcFile, checkenv):
        """
        """
        for host in hostList:
            if resultMap.get(host) != cmdReturn:
                if outputCollect.find("GAUSS-5") == -1:
                    raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % cmd +
                                    " Result:%s.\nError:\n%s" % (resultMap, outputCollect))
                else:
                    raise Exception(outputCollect)
        if checkenv:
            for res in output.split("\n"):
                if res.find("[SUCCESS]") >= 0:
                    continue
                elif res == "":
                    continue
                else:
                    if mpprcFile != "" and mpprcFile is not None:
                        envfile = mpprcFile + " and /etc/profile"
                    else:
                        envfile = "/etc/profile and ~/.bashrc"
                    raise Exception(ErrorCode.GAUSS_518["GAUSS_51808"] % res + "Please check %s." % envfile)

    def getSshStatusOutput(self, cmd, hostList=None, env_file="", gp_path="", ssh_config=""):
        """
        function: Get command status and output
        input : cmd, hostList, env_file, gp_path, ssh_config
        output: resultMap, outputCollect
        """
        if hostList is None:
            hostList = []
        localMode = False
        parallel_num = self.parallNum
        isTimeOut = False
        try:
            mpprcFile, userProfile, osProfile = self.getUserOSProfile(env_file)
            # clean result file
            if os.path.exists(self.__resultFile):
                os.remove(self.__resultFile)

            if gp_path == "":
                GPHOME = self.getGPHOMEPath(osProfile)
            else:
                GPHOME = gp_path.strip()
            psshpre = "python3 %s/script/gspylib/pssh/bin/pssh" % GPHOME
            if ssh_config:
                if os.path.exists(ssh_config) and os.path.isfile(ssh_config):
                    psshpre += ' -x "-F %s" ' % ssh_config

            if len(hostList) == 0:
                sshCmd = self.get_ssh_cmd_without_host_list(osProfile, mpprcFile, userProfile,
                                                            psshpre, parallel_num, cmd)
                hostList = self.hostNames
            else:
                sshCmd = self.get_ssh_cmd_with_host_list(osProfile, mpprcFile, userProfile,
                                                         psshpre, parallel_num, cmd, hostList)
            # single cluster or execute only in local node.
            if len(hostList) == 1 and hostList[0] == socket.gethostname():
                localMode = True
                if os.getuid() == 0 and (mpprcFile == "" or mpprcFile is None):
                    sshCmd = "source %s ; %s 2>&1" % (osProfile, cmd)
                else:
                    sshCmd = "source %s ; source %s; %s 2>&1" % (osProfile, userProfile, cmd)

            (status, output) = subprocess.getstatusoutput(sshCmd)
            # when the pssh is time out, kill parent and child process
            resultMap, outputCollect, isTimeOut = self.handle_result(localMode, cmd, psshpre, hostList,
                                                                     env_file, sshCmd, status, output)
        except Exception as e:
            if not isTimeOut:
                self.clenSshResultFiles()
            raise Exception(str(e))

        for host in hostList:
            if resultMap.get(host) != DefaultValue.SUCCESS:
                if outputCollect.find("GAUSS-5") == -1:
                    outputCollect = ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " Error:\n%s." % outputCollect
                    break

        return resultMap, outputCollect

    def handle_result(self, localMode, cmd, psshpre, hostList, env_file, sshCmd, status, output):
        """
        """
        isTimeOut = False
        resultMap = {}
        if not localMode:
            if output.find("Timed out, Killed by signal 9") > 0:
                isTimeOut = True
                self.timeOutClean(cmd, psshpre, hostList, env_file)
                raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % sshCmd + " Error:\n%s" % output)
            if status != 0:
                raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % sshCmd + f" Error:\n{output}\n status:{status}")

        if localMode:
            if status == 0:
                resultMap[hostList[0]] = DefaultValue.SUCCESS
                outputCollect = "[%s] %s:\n%s" % ("SUCCESS", hostList[0], output)

                if not os.path.exists(self.__outputPath):
                    os.makedirs(self.__outputPath)
                g_file.write_file_with_default_permission(os.path.join(self.__outputPath, hostList[0]), output, False)
            else:
                resultMap[hostList[0]] = DefaultValue.FAILURE
                outputCollect = "[%s] %s:\n%s" % ("FAILURE", hostList[0], output)

                if not os.path.exists(self.__errorPath):
                    os.makedirs(self.__errorPath)
                g_file.write_file_with_default_permission(os.path.join(self.__errorPath, hostList[0]), output, False)
        else:
            resultMap, outputCollect = self.parseSshResult(cmd, hostList, output)
        return resultMap, outputCollect, isTimeOut

    def parseSshOutput(self, hostList):
        """
        function:
          parse ssh output on every host
        input:
          hostList: the hostname list of all hosts
        output:
          a dict, like this "hostname : info of this host"
        hiden info:
          the output info of all hosts
        ppp:
          for a host in hostList
            if outputfile exists
              open file with the same name
              read context into a str
              close file
              save info of this host
            else
              raise exception
          return host info list
        """
        resultMap = {}
        try:
            for host in hostList:
                context = ""
                sshOutPutFile = "%s/%s" % (self.__outputPath, host)
                sshErrorPutFile = "%s/%s" % (self.__errorPath, host)

                if os.path.isfile(sshOutPutFile):
                    with open(sshOutPutFile, "r") as fp:
                        context = fp.read()
                    resultMap[host] = context
                if os.path.isfile(sshErrorPutFile):
                    with open(sshErrorPutFile, "r") as fp:
                        context += fp.read()
                    resultMap[host] = context
                if not os.path.isfile(sshOutPutFile) and not os.path.isfile(sshErrorPutFile):
                    raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] %
                                    "%s or %s" % (sshOutPutFile, sshErrorPutFile))
        except Exception as e:
            raise Exception(str(e))

        return resultMap

    def scpFiles(self, srcFile, targetDir, hostList=None, env_file="", gp_path=""):
        """
        function: copy files to other path
        input : srcFile, targetDir, hostList, env_file, gp_path
        output: NA
        """
        if hostList is None:
            hostList = []
        scpCmd = "source /etc/profile"
        parallel_num = self.parallNum
        try:
            if env_file != "":
                mpprcFile = env_file
            else:
                mpprcFile = DefaultValue.getEnv(DefaultValue.MPPRC_FILE_ENV)
            if mpprcFile != "" and mpprcFile is not None:
                scpCmd += " ; source %s" % mpprcFile

            if gp_path == "":
                cmdpre = "%s ; echo $GPHOME" % scpCmd
                (status, output) = subprocess.getstatusoutput(cmdpre)
                if status != 0 or output is None or output.strip() == "":
                    raise Exception(ErrorCode.GAUSS_518["GAUSS_51802"] % "GPHOME")
                GPHOME = output.strip()
            else:
                GPHOME = gp_path.strip()
            pscppre = "python3 %s/script/gspylib/pssh/bin/pscp" % GPHOME

            chmod_cmd = f"chmod {DefaultValue.FILE_MODE} '{self.__resultFile}'"
            if len(hostList) == 0:
                scpCmd += " ; %s -r -v -t %s -p %s -h %s -o %s -e %s %s %s 2>&1 | tee %s && %s" % \
                          (pscppre, self.__timeout, parallel_num, self.__hostsFile, self.__outputPath,
                           self.__errorPath, srcFile, targetDir, self.__resultFile, chmod_cmd)
                hostList = self.hostNames
            else:
                scpCmd += " ; %s -r -v -t %s -p %s -H %s -o %s -e %s %s %s 2>&1 | tee %s && %s" % \
                          (pscppre, self.__timeout, parallel_num, " -H ".join(hostList), self.__outputPath,
                           self.__errorPath, srcFile, targetDir, self.__resultFile, chmod_cmd)
            (status, output) = DefaultValue.retryGetstatusoutput(scpCmd)
            if status != 0:
                raise Exception(ErrorCode.GAUSS_502["GAUSS_50216"] % ("file [%s]" % srcFile) +
                                " To directory: %s." % targetDir + " Error:\n%s" % output)
            if output.find("Timed out") > 0:
                raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % scpCmd + " Error:\n%s" % output)

            # ip and host name should match here
            resultMap, outputCollect = self.parseSshResult(scpCmd, hostList, output)
        except Exception as e:
            self.clenSshResultFiles()
            raise Exception(str(e))

        fail_host_list = [host for host in hostList if resultMap.get(host) != DefaultValue.SUCCESS]
        if fail_host_list:
            error_info = (ErrorCode.GAUSS_502["GAUSS_50216"] % ("file [%s]" % srcFile) + " To directory: %s." %
                          targetDir + " Command: %s.\nError:\n%s" % (scpCmd, outputCollect))
            if self.parallNum == 1:
                raise Exception(error_info)
            else:
                self.retry_scp_file(srcFile, targetDir, fail_host_list, error_info)

    def retry_scp_file(self, src_file, target_dir, fail_host_list, error_info):
        backup_parall_num = self.parallNum
        self.parallNum = 1
        try:
            self.scpFiles(src_file, target_dir, fail_host_list)
        except Exception as e:
            error_info += "\n Retry hostnames [%s], Retry result:\n%s" % (fail_host_list, str(e))
            raise Exception(error_info)
        finally:
            self.parallNum = backup_parall_num

    def checkRemoteFileExist(self, node, fileAbsPath, mpprcFile):
        """
        check remote node exist file
        this method depend on directory permission 'x'
        if exist return true,else return false
        """
        sshcmd = "if [ -e '%s' ];then echo 'exist tar file yes flag';" \
                 "else echo 'exist tar file no flag';fi" % fileAbsPath
        if node != socket.gethostname():
            (_, outputCollect) = self.getSshStatusOutput(sshcmd, [node], mpprcFile)
        else:
            (_, outputCollect) = subprocess.getstatusoutput(sshcmd)
        if 'exist tar file yes flag' in outputCollect:
            return True
        elif 'exist tar file no flag' in outputCollect:
            return False
        else:
            raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % sshcmd + "On node %s" % node)

    def __writeHostFiles(self):
        """
        function: Write all hostname to a file
        input : NA
        output: NA
        """
        flags = os.O_WRONLY | os.O_CREAT
        modes = stat.S_IWUSR | stat.S_IRUSR
        try:
            with os.fdopen(os.open(self.__hostsFile, flags, modes), "w") as fp:
                for host in self.hostNames:
                    fp.write("%s\n" % host)
        except Exception as e:
            g_file.removeFile(self.__hostsFile)
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50205"] % "host file" + " Error: \n%s" % str(e))

        cmd = f"chmod {DefaultValue.FILE_MODE} '{self.__hostsFile}'"
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0:
            g_file.removeFile(self.__hostsFile)
            raise Exception(ErrorCode.GAUSS_501["GAUSS_50107"] % self.__hostsFile + " Error: \n%s" % output)

    def __readCmdResult(self, resultFile, hostNum, cmd, output_info=None):
        """
        function: Read command result
        input : resultFile, hostNum, cmd
        output: resultMap
        """
        resultMap = {}
        try:
            with open(resultFile, "r") as fp:
                lines = fp.readlines()
            context = "".join(lines)
            for line in lines:
                resultPair = line.strip().split(" ")
                if len(resultPair) >= 4 and resultPair[2] == "[FAILURE]":
                    resultMap[resultPair[3]] = "Failure"
                if len(resultPair) >= 4 and resultPair[2] == "[SUCCESS]":
                    resultMap[resultPair[3]] = "Success"

            if len(resultMap) != hostNum:
                raise Exception(ErrorCode.GAUSS_516["GAUSS_51637"] %
                                ("valid return item number [%d]" % len(resultMap), "host number[%d]" % hostNum) +
                                " The return result:\n%s, output information:\n%s" % (context, output_info))
        except Exception as e:
            raise Exception(str(e))

        return resultMap

    def setTimeOut(self, timeout):
        """
        function: Set a new timeout value for ssh tool.
        :param timeout: The new timeout value in seconds.
        :return: void
        """
        self.__timeout = timeout

    def getTimeOut(self):
        """
        function: Get timeout value of sshtool in seconds.
        :return: timeout in seconds
        """
        return self.__timeout

    def getLogFile(self):
        """
        function: Get log file path of sshtool.
        :return: the path of log file
        """
        return self.__logFile

if __name__ == '__main__':
    hosts = ["dws-0060", "dws-0061", "dws-0062"]
    ssh = SshTool(hosts, "aa")
    ssh.scpFiles("/home/aa", "/home/perfadm")
