#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c): 2012-2017, Huawei Tech. Co., Ltd.
# Description  : cluster command is a utility with a lot of cluster command functions
#############################################################################

try:
    import os
    import socket
    import sys
    import re
    import time
    import subprocess
    from ctypes import cdll, CDLL, c_char_p, c_void_p, c_int, string_at
    from datetime import datetime
    from random import sample
    from multiprocessing.dummy import Pool as ThreadPool

    localDirPath = os.path.dirname(os.path.realpath(__file__))
    sys.path.insert(0, localDirPath + "/../../../../lib")
    sys.path.append(localDirPath + "/../../../")
    from gspylib.common.ErrorCode import ErrorCode
    from gspylib.os.gsplatform import g_Platform
    from gspylib.os.gsfile import g_file
    from gspylib.os.gsOSlib import g_OSlib
    from gspylib.os.gsnetwork import g_network
    from gspylib.os.platform import support_platform
    from gspylib.common.common.default_value import DefaultValue
except ImportError as ie:
    sys.exit("[GAUSS-52200] : Unable to import module: %s." % str(ie))


class ClusterCommand():
    """
    Common for cluster command
    """

    def __init__(self):
        pass

    # gs_sshexkey execution takes total steps
    TOTAL_STEPS_SSHEXKEY = 11
    # gs_preinstall -L execution takes total steps
    TOTAL_STEPS_PREINSTALL_L = 14
    # gs_preinstall execution takes total steps
    TOTAL_STEPS_PREINSTALL = 20
    # gs_install execution takes total steps
    TOTAL_STEPS_INSTALL = 7
    # gs_om -t managecn -m add execution takes total steps
    TOTAL_STEPS_OM_ADD = 20
    # gs_om -t managecn -m delete execution takes total steps
    TOTAL_STEPS_OM_DELETE = 16
    # gs_om -t changeip execution takes total steps
    TOTAL_STEPS_OM_CHANGEIP = 11
    # gs_expand -t dilatation execution takes total steps
    TOTAL_STEPS_EXPAND_DILA = 17
    # gs_expand -t redistribute execution takes total steps
    TOTAL_STEPS_EXPAND_REDIS = 6
    # gs_shrink -t entry1_percontraction execution takes total steps
    TOTAL_STEPS_SHRINK_FIRST = 9
    # gs_shrink -t entry2_redistributre execution takes total steps
    TOTAL_STEPS_SHRINK_SECOND = 8
    # gs_shrink -t entry3_postcontraction execution takes total steps
    TOTAL_STEPS_SHRINK_THIRD = 7
    # gs_replace -t warm-standby execution takes total steps
    TOTAL_STEPS_REPLACE_WARM_STANDBY = 11
    # gs_replace -t warm-standby rollback replace execution takes total steps
    TOTAL_STEPS_REPLACE_WARM_STANDBY_REPLACE = 9
    # gs_replace -t warm-standby rollback install execution takes total steps
    TOTAL_STEPS_REPLACE_WARM_STANDBY_INSTALL = 7
    # gs_replace -t warm-standby rollback config execution takes total steps
    TOTAL_STEPS_REPLACE_WARM_STANDBY_CONFIG = 6
    # gs_replace -t install execution takes total steps
    TOTAL_STEPS_REPLACE_INSTALL = 6
    # gs_replace -t config execution takes total steps
    TOTAL_STEPS_REPLACE_CONFIG = 6
    # gs_replace -t start execution takes total steps
    TOTAL_STEPS_REPLACE_START = 3
    # gs_uninstall execution takes total steps
    TOTAL_STEPS_UNINSTALL = 8
    # gs_upgradectl -t binary-upgrade execution takes total steps
    TOTAL_STEPS_BINARY_BUPGRADECTL = 15
    # gs_postuninstall execution takes total steps
    TOTAL_STEPS_POSTUNINSTALL = 3
    # warm-standby rollback to flag of begin warm standby
    WARM_STEP_INIT = "Begin warm standby"
    # warm-standby rollback to flag of replace IP finished
    WARM_STEP_REPLACEIPS = "Replace IP finished"
    # warm-standby rollback to flag of install warm standby nodes finished
    WARM_STEP_INSTALL = "Install warm standby nodes finished"
    # warm-standby rollback to flag of configure warm standby nodes finished
    WARM_STEP_CONFIG = "Configure warm standby nodes finished"
    # rollback to flag of start cluster
    INSTALL_STEP_CONFIG = "Config cluster"
    # rollback to flag of start cluster
    INSTALL_STEP_START = "Start cluster"

    libc = None

    @staticmethod
    def parseCmCtlCommand(nodeId, timeout, datadir, azName, stopMode):
        cmd = ""
        # check node id
        if nodeId > 0:
            cmd += " -n %d" % nodeId
        # check data directory
        if datadir != "":
            cmd += " -D %s" % datadir
        # check timeout
        if timeout > 0:
            cmd += " -t %d" % timeout
        # azName
        if azName != "":
            cmd += " -z%s" % azName
        # check stop mode
        if stopMode != "":
            cmd += " -m %s" % stopMode
        return cmd

    @staticmethod
    def getStartCmd(user, nodeId=0, timeout=DefaultValue.TIMEOUT_CLUSTER_START, datadir="", azName=""):
        """
        function : Start all cluster or a node
        input : String,int,String,String
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        cmd = "%s %s ; cm_ctl start %s" % (g_Platform.getSourceCmd(),
                                           userProfile,
                                           ClusterCommand.parseCmCtlCommand(nodeId, timeout, datadir, azName, ""))
        return cmd

    @staticmethod
    def getStopCmd(user, nodeId=0, stopMode="", timeout=DefaultValue.TIMEOUT_CLUSTER_STOP, datadir="", azName=""):
        """
        function : Stop all cluster or a node
        input : String,int,String,String
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        cmd = "%s %s ; cm_ctl stop %s" % (g_Platform.getSourceCmd(),
                                          userProfile,
                                          ClusterCommand.parseCmCtlCommand(nodeId, timeout, datadir, azName, stopMode))
        return cmd

    @staticmethod
    def getSwitchOverCmd(user, nodeId, datadir, azName="", timeout=None):
        """
        function : Get the command of switching over standby instance
        input : String,int,String
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        if (azName != ""):
            executeCmd = "cm_ctl switchover -z%s" % azName
        else:
            executeCmd = "cm_ctl switchover -n %d -D %s" % (nodeId, datadir)
        if (timeout):
            executeCmd += " -t %d" % timeout
        cmd = g_Platform.getExecuteCmdWithUserProfile(user, userProfile, executeCmd, False)

        return cmd

    @staticmethod
    def getQuerySwitchOverCmd(user):
        """
        function : Query Switch over
        input : String
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        executeCmd = "cm_ctl query -v -C -s -i -d"
        # build shell command
        cmd = g_Platform.getExecuteCmdWithUserProfile(user, userProfile, executeCmd, False)
        return cmd

    @staticmethod
    def getResetSwitchOverCmd(user, timeout=DefaultValue.TIMEOUT_CLUSTER_SWITCHRESET):
        """
        function : Reset Switch over
        input : String,String
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        executeCmd = "cm_ctl switchover -a -t %d" % timeout
        # build shell command
        cmd = g_Platform.getExecuteCmdWithUserProfile(user, userProfile, executeCmd, False)

        return cmd

    @staticmethod
    def getRedisCmd(user, port, jobs=1, timeout=None, enableVacuum="", enableFast="",
                    redisRetry="", buildTable=False, mode="", host="", database="postgres", is_check_consistency=False,
                    redis_track_task=""):
        """
        funciton : Get the command of gs_redis
        input : String,int
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        database = database.replace('$', '\$')
        cmd = "%s %s ; gs_redis -u %s -p %s -d %s -j %d %s %s %s" % \
              (g_Platform.getSourceCmd(), userProfile, user, str(port),
               database, jobs, enableVacuum, enableFast, redisRetry)
        # check timeout
        if (timeout is not None):
            cmd += " -t %d" % timeout
        # check buildTable
        if buildTable:
            cmd += " -v"
        else:
            cmd += " -r"
        # check mode
        if (len(mode)):
            cmd += " -m %s" % mode
        if is_check_consistency:
            cmd += " -C "
        if redis_track_task:
            cmd += " -I %s" % redis_track_task
        return cmd

    @staticmethod
    def getRedisCmdWithPwd(user, redispasswd, port, jobs=1, timeout=None, enableVacuum="", enableFast="",
                           redisRetry="", buildTable=False, mode="", host="", database="postgres", tnum=0,
                           is_check_consistency=False, redis_track_task=""):
        """
        funciton : Get the command of gs_redis with password for redisuser
        input : String,int
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        database = database.replace('$', '\$')
        cmd = "%s %s; " % (g_Platform.getSourceCmd(), userProfile)
        redis_passwd_info = "" if (mode == "read-only") else "-W %s" % redispasswd
        cmd += "gs_redis -u %s %s -p %s -d %s -j %d %s %s %s" % \
               (user, redis_passwd_info, str(port), database, jobs,
                enableVacuum, enableFast, redisRetry)
        # check timeout
        if (timeout is not None):
            cmd += " -t %d" % timeout
        # check buildTable
        if buildTable:
            cmd += " -v"
        else:
            cmd += " -r"
        # check mode
        if (len(mode)):
            cmd += " -m %s" % mode
        # check tnum
        if (tnum > 0):
            cmd += " -s %d" % tnum
        if is_check_consistency:
            cmd += " -C "
        if redis_track_task:
            cmd += " -I %s" % redis_track_task
        return cmd

    @staticmethod
    def getQueryStatusCmd(user, nodeId=0, outFile="", showDetail=True):
        """
        function : Get the command of querying status of cluster or node
        input : String
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        cmd = "%s %s ; cm_ctl query" % (g_Platform.getSourceCmd(), userProfile)
        # check node id
        if (nodeId > 0):
            cmd += " -n %d" % nodeId
        # check -v
        if (showDetail):
            cmd += " -v"
            # check out put file
        if (outFile != ""):
            cmd += " > %s" % outFile

        return cmd

    @staticmethod
    def getQueryStatusCmdForDisplay(user, nodeId=0, outFile="", clusterType=DefaultValue.CLUSTER_TYPE_MASTER_STANDBY,
                                    showDetail=True):
        """
        function : Get the command of querying status of cluster or node
        input : String
        output : String
        """
        userProfile = DefaultValue.getMpprcFile()
        cmd = "%s %s ; cm_ctl query" % (g_Platform.getSourceCmd(), userProfile)
        # check node id
        if (nodeId > 0):
            cmd += " -v -n %d" % nodeId
        # check -v
        if (showDetail):
            if (clusterType == DefaultValue.CLUSTER_TYPE_SINGLE_PRIMARY_MULTI_STANDBY):
                cmd += " -v -C -i -d -z ALL"
            else:
                cmd += " -v -C -i -d"

        # check out put file
        if (outFile != ""):
            cmd += " > %s" % outFile
        return cmd

    @staticmethod
    def findErrorInSqlFile(sqlFile, output):
        """
        function : Find error in the sql file
        input : String,String
        output : String
        """
        GSQL_BIN_FILE = "gsql"
        # init flag
        ERROR_MSG_FLAG = "(ERROR|FATAL|PANIC)"
        GSQL_ERROR_PATTERN = "^%s:%s:(\d*): %s:.*" % (GSQL_BIN_FILE, sqlFile, ERROR_MSG_FLAG)
        pattern = re.compile(GSQL_ERROR_PATTERN)
        for line in output.split("\n"):
            line = line.strip()
            result = pattern.match(line)
            if (result is not None):
                return True
        return False

    @staticmethod
    def findErrorInSql(output):
        """
        function : Find error in sql
        input : String
        output : boolean
        """
        # init flag
        ERROR_MSG_FLAG = "(ERROR|FATAL|PANIC)"
        ERROR_PATTERN = "^%s:.*" % ERROR_MSG_FLAG
        pattern = re.compile(ERROR_PATTERN)

        for line in output.split("\n"):
            line = line.strip()
            result = pattern.match(line)
            if (result is not None):
                return True
        return False

    @staticmethod
    def getSQLCommand(port,
                      database=DefaultValue.DEFAULT_DB_NAME,
                      gsqlBin="gsql",
                      is_inplace_upgrade=True,
                      application_name="OM"):
        """
        function : get SQL command
        input : port, database
        output : cmd
        """
        if is_inplace_upgrade:
            cmd = DefaultValue.SQL_EXEC_COMMAND_WITHOUT_USER_FOR_UPGRADE % (gsqlBin, port, database, application_name)
            return cmd

        dbAdminPwd = DefaultValue.getDbAdminPwd()
        if dbAdminPwd == "":
            cmd = DefaultValue.SQL_EXEC_COMMAND_WITHOUT_USER % (gsqlBin, port, database)
        else:
            cmd = DefaultValue.SQL_EXEC_COMMAND_WITH_USER % (gsqlBin, port, database, DefaultValue.RUBY, dbAdminPwd)
        return cmd

    @staticmethod
    def execSQLCommand(sql, user, host, port, database="postgres",
                       dwsFlag=False, option="", is_inplace_upgrade=True, retry_times=1,
                       sql_file_name="", query_result_file_name=""):
        """
        function : Execute sql command
        input : String,String,String,int
        output : String
        notice : notice that this function will generate a sql_file and a query_result_file
                 to store the query and result. If 9 signal is called outside during processing,
                 sql_file and query_result_file will be remained on disc. You can use sql_file_name
                 and query_result_file_name to assign the target files.
        """
        database = database.replace('$', '\$')
        current_time = datetime.utcnow().strftime("%Y-%m-%d_%H%M%S%f")
        pid = os.getpid()
        # init SQL query file
        if sql_file_name == "":
            sql_file = os.path.join(DefaultValue.getTmpDirFromEnv(user),
                                    "gaussdb_query.sql_%s_%s_%s" % (str(port), str(current_time), str(pid)))
        else:
            sql_file = os.path.join(DefaultValue.getTmpDirFromEnv(user),
                                    sql_file_name)

        # init SQL result file
        if query_result_file_name == "":
            query_result_file = os.path.join(DefaultValue.getTmpDirFromEnv(user),
                                             "gaussdb_result.sql_%s_%s_%s" % (str(port), str(current_time), str(pid)))
        else:
            query_result_file = os.path.join(DefaultValue.getTmpDirFromEnv(user),
                                             query_result_file_name)

        if os.path.exists(sql_file) or os.path.exists(query_result_file):
            DefaultValue.cleanFile("%s,%s" % (query_result_file, sql_file))

        # create an empty sql query file
        try:
            g_file.createFile(sql_file, DefaultValue.KEY_FILE_MODE)
        except Exception as ex:
            if os.path.exists(sql_file):
                os.remove(sql_file)
            return 1, str(ex)

        # write the SQL command into sql query file
        if dwsFlag:
            sql = "set cgroup_name='Rush';" + sql
        try:
            g_file.write_file_with_default_permission(sql_file, sql)
        except Exception as ex:
            DefaultValue.cleanFile(sql_file)
            return 1, str(ex)

        user_profile = DefaultValue.getMpprcFile()
        host_para = ("-h %s" % host) if host != "" else ""
        # build shell command
        # if the user is root, switch the user to execute
        gsql_cmd = ClusterCommand.getSQLCommand(port, database, is_inplace_upgrade=is_inplace_upgrade)
        execute_cmd = "%s %s -f '%s' --output '%s' -t -A -X %s" % \
                      (gsql_cmd, host_para, sql_file, query_result_file, option)
        cmd = g_Platform.getExecuteCmdWithUserProfile(user, user_profile, execute_cmd, False)

        success = False
        status, output = 1, ""
        for _ in range(retry_times):
            (status, output) = subprocess.getstatusoutput(cmd)
            if status == 0 and not ClusterCommand.findErrorInSqlFile(sql_file, output):
                success = True
                break
            time.sleep(1)

        if not success:
            DefaultValue.cleanFile("%s,%s" % (query_result_file, sql_file))
            return 1, output

        # read the content of query result file.
        try:
            with open(query_result_file, "r") as fp:
                row_list = fp.readlines()
            ClusterCommand.filter_notices_info(row_list)
        except Exception as ex:
            DefaultValue.cleanFile("%s,%s" % (query_result_file, sql_file))
            return 1, str(ex)

        # remove local sql_file
        DefaultValue.cleanFile("%s,%s" % (query_result_file, sql_file))

        return 0, "".join(row_list)[:-1]

    @staticmethod
    def findTupleErrorInSqlFile(sqlFile, output):
        """
        function : find tuple concurrently updated error in file
        input : sqlFile, output
        output : True, False
        """
        ERROR_TUPLE_PATTERN = "^gsql:(.*)tuple concurrently updated(.*)"
        pattern = re.compile(ERROR_TUPLE_PATTERN)
        for line in output.split("\n"):
            line = line.strip()
            result = pattern.match(line)
            if result is not None:
                return True
        return False

    @staticmethod
    def retry_sql_cmd_for_remote_sql(user, host, port, sqlFile, queryResultFile, ignoreError, database,
                                     is_inplace_upgrade, local_host, enable_retry):
        """
        function: retry cmd for remote sql
        input: user, host, port, sqlFile, queryResultFile, ignoreError, database,
               is_inplace_upgrade, local_host, enable_retry
        output: 0, 1
        """
        status1 = 0
        output1 = ""
        cmd = ClusterCommand.getRemoteSQLCmd(user, host, port, sqlFile,
                                             queryResultFile, ignoreError, database,
                                             is_inplace_upgrade=is_inplace_upgrade)
        for _ in range(DefaultValue.RE_TIMES):
            (status1, output1) = subprocess.getstatusoutput(cmd)
            if ClusterCommand.findErrorInSqlFile(sqlFile, output1):
                status1 = 1
                if ClusterCommand.findTupleErrorInSqlFile(sqlFile, output1):
                    time.sleep(1)  # find tuple error --> retry
                elif enable_retry:
                    time.sleep(1)
                else:  # find error not tuple error
                    break
            else:  # not find error
                break

        # if failed to execute gsql, then clean the sql query file on current node and other node
        if status1 != 0:
            ClusterCommand.cleanSQLFileAndResultFile(queryResultFile, sqlFile, host)
            return status1, output1

        if str(local_host) != str(host):
            cmd = g_Platform.getScpCmd() + support_platform.BLANK_SPACE + \
                  g_network.makeSCPHost(host) + support_platform.COLON + \
                  queryResultFile + support_platform.BLANK_SPACE + DefaultValue.getTmpDirFromEnv(user) + "/"

            (status, output) = subprocess.getstatusoutput(cmd)
            if status != 0:
                output = "%s\n%s" % (cmd, output)
                ClusterCommand.cleanSQLFileAndResultFile(queryResultFile, sqlFile, host)
                return status, output

        # read the content of query result file.
        try:
            with open(queryResultFile, "r") as fp:
                row_list = fp.readlines()
            ClusterCommand.filter_notices_info(row_list)
        except Exception as ex:
            ClusterCommand.cleanSQLFileAndResultFile(queryResultFile, sqlFile, host)
            return 1, str(ex)

        ClusterCommand.cleanSQLFileAndResultFile(queryResultFile, sqlFile, host)
        return 0, "".join(row_list)[:-1]

    @staticmethod
    def filter_notices_info(sql_result_list):
        pattern = re.compile(r'^(WARNING|NOTICE)\s*:.*, please change the password')
        if sql_result_list and pattern.match(sql_result_list[0]):
            sql_result_list.pop(0)

    @staticmethod
    def remoteSQLCommand(sql, user, host, port, ignoreError=True, database="postgres", dwsFlag=False,
                         useTid=False, is_inplace_upgrade=True, enable_retry=False):
        """
        function : Execute sql command on remote host
        input : String,String,String,int
        output : String,String
        """
        database = database.replace('$', '\$')
        current_time = datetime.utcnow().strftime("%Y-%m-%d_%H%M%S%f")
        pid = os.getpid()
        # clean old sql file
        # init SQL query file
        sql_file = os.path.join(DefaultValue.getTmpDirFromEnv(user),
                                "gaussdb_remote_query.sql_%s_%s_%s" %
                                (str(port), str(current_time), str(pid)))
        # init SQL result file
        query_result_file = os.path.join(DefaultValue.getTmpDirFromEnv(user),
                                         "gaussdb_remote_result.sql_%s_%s_%s" %
                                         (str(port), str(current_time), str(pid)))
        if useTid:
            thread_pid = CDLL('libc.so.6').syscall(186)
            sql_file = sql_file + str(thread_pid)
            query_result_file = query_result_file + str(thread_pid)
        if os.path.exists(sql_file) or os.path.exists(query_result_file):
            DefaultValue.cleanFile("%s,%s" % (query_result_file, sql_file))

        # create new sql file
        if os.getuid() == 0:
            cmd = "su - %s -c 'touch %s && chmod %s %s'" % (user, sql_file, DefaultValue.KEY_FILE_MODE, sql_file)
        else:
            cmd = "touch %s && chmod %s %s" % (sql_file, DefaultValue.KEY_FILE_MODE, sql_file)
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0:
            output = "%s\n%s" % (cmd, output)
            if os.path.exists(sql_file) or os.path.exists(query_result_file):
                DefaultValue.cleanFile("%s,%s" % (query_result_file, sql_file))
            return status, output

        if dwsFlag:
            sql = "set cgroup_name='Rush';" + sql

        # write the SQL command into sql query file
        try:
            g_file.write_file_with_default_permission(sql_file, sql)
        except Exception as ex:
            DefaultValue.cleanFile(sql_file)
            return 1, str(ex)

        # send new sql file to remote node if needed
        local_host = socket.gethostname()
        if str(local_host) != str(host):
            cmd = g_Platform.getRemoteCopyCmd(sql_file, sql_file, host)
            if os.getuid() == 0 and user != "":
                cmd = "su - %s \"%s\"" % (user, cmd)
            (status, output) = subprocess.getstatusoutput(cmd)
            if status != 0:
                DefaultValue.cleanFile("%s,%s" % (query_result_file, sql_file))
                output = "%s\n%s" % (cmd, output)
                return status, output
        status, output = ClusterCommand.retry_sql_cmd_for_remote_sql(
            user, host, port, sql_file, query_result_file, ignoreError, database,
            is_inplace_upgrade, local_host, enable_retry)
        return status, output

    @staticmethod
    def getRemoteSQLCmd(user,
                        host,
                        port,
                        sqlFile,
                        queryResultFile,
                        ignoreError,
                        database,
                        is_inplace_upgrade=True):
        """
        function : get the cmd for excute remoteSQL
        input : String,String,String,int
        output : String,String
        """
        # execute sql file
        mpprcFile = DefaultValue.getMpprcFile()
        gsql_cmd = ClusterCommand.getSQLCommand(port,
                                                database,
                                                is_inplace_upgrade=is_inplace_upgrade)
        localHost = socket.gethostname()
        if (str(localHost) != str(host)):
            sshCmd = g_Platform.getSshCmd(host)
            if (os.getuid() == 0 and user != ""):
                cmd = " %s 'su - %s -c \"" % (sshCmd, user)
                if (mpprcFile != "" and mpprcFile is not None):
                    cmd += "source %s;" % mpprcFile
                cmd += "%s -f %s --output %s -t -A -X \"'" % (gsql_cmd, sqlFile, queryResultFile)
            else:
                cmd = "%s '" % sshCmd
                if (mpprcFile != "" and mpprcFile is not None):
                    cmd += "source %s;" % mpprcFile
                cmd += "%s -f %s --output %s -t -A -X '" % (gsql_cmd, sqlFile, queryResultFile)
        else:
            if (os.getuid() == 0 and user != ""):
                cmd = "su - %s -c \"" % user
                if (mpprcFile != "" and mpprcFile is not None):
                    cmd += "source %s;" % mpprcFile
                cmd += "%s -f %s --output %s -t -A -X \"" % (gsql_cmd, sqlFile, queryResultFile)
            else:
                cmd = ""
                if (mpprcFile != "" and mpprcFile is not None):
                    cmd += "source %s;" % mpprcFile
                cmd += "%s -f %s --output %s -t -A -X " % (gsql_cmd, sqlFile, queryResultFile)

        if (ignoreError):
            cmd += " 2>/dev/null"
        return cmd

    @staticmethod
    def cleanSQLFileAndResultFile(queryResultFile, sqlFile, host):
        """
        function : clean sqlfile and resultfile on local and remote
        input : String,String,String
        output : NA
        """
        localHost = socket.gethostname()
        # remove local sqlFile
        DefaultValue.cleanFile("%s,%s" % (queryResultFile, sqlFile))
        # remove remote sqlFile
        if (str(localHost) != str(host)):
            DefaultValue.cleanFile("%s,%s" % (queryResultFile, sqlFile), host)

    @staticmethod
    def checkSqlConnect(user, host, port, retryTimes=DefaultValue.DEFAULT_RETRY_TIMES_GS_CTL, sql=None,
                        dwsFlag=False):
        """
        After the operation "gs_ctl start" has returned the success information, we will try to connect the database
         and execute some sql to check the connection.

        :param user:        The input database user.
        :param host:        The input database host or ip address.
        :param port:        The input database port.
        :param retryTimes:  The times of attempts to retry the operation.
        :param sql:         The SQL statements used in retry operation.
        :param dwsFlag:     Whether the cluster is in the dws mode.

        :type user:         str
        :type host:         str
        :type port:         int
        :type retryTimes:   int
        :type sql:          str | None
        :type dwsFlag:      bool

        :return:    Return the query result.
        :rtype:     str
        """
        # Set default query sql string.
        if sql is None:
            sql = "select version();"

        status = 0
        output = ""
        for _ in range(0, retryTimes):
            status, output = ClusterCommand.remoteSQLCommand(sql,
                                                             user,
                                                             host,
                                                             port,
                                                             False,
                                                             dwsFlag=dwsFlag,
                                                             is_inplace_upgrade=True)
            if status == 0 and output != "":
                return output

            time.sleep(2)

        raise Exception(ErrorCode.GAUSS_516["GAUSS_51632"] % "check instance connection" +
                        " Status:%s\nOutput:%s" % (status, output))

    @staticmethod
    def remoteShellCommand(shell, user, hostname):
        """
        function : Execute shell command on remote host
        input : String,String,String
        output : String,String
        """
        currentTime = datetime.utcnow().strftime("%Y-%m-%d_%H%M%S%f")
        randomnum = ''.join(sample('0123456789', 3))
        pid = os.getpid()
        shFile = os.path.join(DefaultValue.getTmpDirFromEnv(user),
                              "gaussdb_remote_shell.sh_%s_%s_%s_%s" %
                              (str(hostname), str(currentTime), str(pid), str(randomnum)))
        if os.path.exists(shFile):
            DefaultValue.cleanFile(shFile)

        # create new sh file
        if os.getuid() == 0:
            cmd = "su - %s -c 'touch %s && chmod %s %s'" % (user, shFile, DefaultValue.KEY_FILE_MODE, shFile)
        else:
            cmd = "touch %s && chmod %s %s" % (shFile, DefaultValue.KEY_FILE_MODE, shFile)
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0:
            if os.path.exists(shFile):
                DefaultValue.cleanFile(shFile)
            output = "%s\n%s" % (cmd, output)
            return status, output

        try:
            g_file.write_file_with_default_permission(shFile, shell)
        except Exception as e:
            DefaultValue.cleanFile(shFile)
            return 1, str(e)

        # send new sh file to remote node if needed
        localHost = socket.gethostname()
        if str(localHost) != str(hostname):
            if os.getuid() == 0:
                cmd = """su - %s -c "export LD_LIBRARY_PATH=/lib64:$LD_LIBRARY_PATH; scp '%s' '%s':'%s'" """ % (
                    user, shFile, g_network.makeSCPHost(hostname), shFile)
            else:
                cmd = "export LD_LIBRARY_PATH=/lib64:$LD_LIBRARY_PATH; scp '%s' '%s':'%s'" % (
                    shFile, g_network.makeSCPHost(hostname), shFile)
            (status, output) = subprocess.getstatusoutput(cmd)
            if status != 0:
                DefaultValue.cleanFile(shFile)
                output = "%s\n%s" % (cmd, output)
                return status, output

        # execute sh file
        cmd = ClusterCommand.getremoteShellCmd(localHost, hostname, user, shFile)
        (status, output) = subprocess.getstatusoutput(cmd)
        # clean tmp file
        DefaultValue.cleanFile(shFile)
        if str(localHost) != str(hostname):
            DefaultValue.cleanFile(shFile, hostname)

        return status, output

    @staticmethod
    def getremoteShellCmd(localHost, hostname, user, shFile):
        """
        function : get remote shell command
        input :
        output : String
        """
        if (str(localHost) != str(hostname)):
            mpprcFile = DefaultValue.getMpprcFile()
            if (os.getuid() == 0):
                cmd = "export LD_LIBRARY_PATH=/lib64:$LD_LIBRARY_PATH; ssh %s -n %s 'su - %s -c \"" % \
                      (hostname, DefaultValue.SSH_OPTION, user)
                if (mpprcFile != "" and mpprcFile is not None):
                    cmd += "source %s;" % mpprcFile
                cmd += "sh %s\"'" % shFile
            else:
                cmd = "export LD_LIBRARY_PATH=/lib64:$LD_LIBRARY_PATH; " \
                      "ssh %s -n %s '" % (hostname, DefaultValue.SSH_OPTION)
                if (mpprcFile != "" and mpprcFile is not None):
                    cmd += "source %s;" % mpprcFile
                cmd += "sh %s'" % shFile
        else:
            mpprcFile = DefaultValue.getMpprcFile()
            if (os.getuid() == 0):
                cmd = "su - %s -c '" % user
                if (mpprcFile != "" and mpprcFile is not None):
                    cmd += "source %s;" % mpprcFile
                cmd += "sh %s'" % shFile
            else:
                cmd = ""
                if (mpprcFile != "" and mpprcFile is not None):
                    cmd += "source %s;" % mpprcFile
                cmd += "sh %s" % shFile
        return cmd

    @staticmethod
    def confirmClusterNotRunning(g_clusterInfo, g_sshTool):
        """
        function : Confirm the cluster is not running
        input: cluster class , sshTool class
        output: NA
        """
        nodes = g_clusterInfo.getClusterNodeNames()
        # olap process
        CMSProcess = "%s/bin/cm_server" % g_clusterInfo.appPath
        CMAProcess = "%s/bin/cm_agent" % g_clusterInfo.appPath
        GTMProcess = "%s/bin/gs_gtm" % g_clusterInfo.appPath
        CNDNProcess = "%s/bin/gaussdb" % g_clusterInfo.appPath
        # init instance lists
        instanceList = [CMSProcess, CMAProcess, GTMProcess, CNDNProcess]
        for i in instanceList:
            # find the instance process
            cmd = DefaultValue.getRuningInstNum(i)
            (resultMap, _) = g_sshTool.getSshStatusOutput(cmd, nodes)
            outputMap = g_sshTool.parseSshOutput(nodes)
            for node in resultMap.keys():
                outstr = outputMap[node].strip().split('\n')[0].strip()
                if outstr.isdigit() and int(outstr) >= 1:
                    raise Exception(ErrorCode.GAUSS_516["GAUSS_51625"] + ' The process of %s on host %s.' % (i, node))

    @staticmethod
    def CopyClusterStatic():
        """
        function : Copy cluster_static_config_bak file to cluster_static_config
        input : NA
        output: NA
        """
        gaussHome = DefaultValue.getEnv("GAUSSHOME")
        DefaultValue.checkPathVaild(gaussHome)
        staticConfig = "%s/bin/cluster_static_config" % gaussHome
        staticConfig_bak = "%s/bin/cluster_static_config_bak" % gaussHome
        if (os.path.exists(staticConfig_bak) and not os.path.exists(staticConfig)):
            g_file.cpFile(staticConfig_bak, staticConfig)

    @staticmethod
    def getchangeDirModeCmd(user_dir):
        """
        function : change directory permission
        input : user_dir
        output: NA
        """
        # Use "find -exec" to mask special characters
        cmdDir = "find '%s' -type d -exec chmod '%s' {} \;" % (user_dir, DefaultValue.DIRECTORY_MODE)
        (status, diroutput) = subprocess.getstatusoutput(cmdDir)
        if (status != 0):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] % user_dir + " Error: \n%s" % diroutput)

    @staticmethod
    def getchangeFileModeCmd(user_dir):
        """
        function : change log file permission
        input : user_dir
        output: NA
        """
        # Use "find -exec" to mask special characters
        cmdFile = "find '%s' -type f -name '*.log' -exec chmod '%s' {} \;" % (user_dir, DefaultValue.KEY_FILE_MODE)
        (status, fileoutput) = subprocess.getstatusoutput(cmdFile)
        if (status != 0):
            raise Exception(ErrorCode.GAUSS_502["GAUSS_50201"] % "log file" +
                            " Directory:%s." % user_dir + " Error: \n%s" % fileoutput)

    @staticmethod
    def countTotalSteps(script, act="", model=""):
        """
        function: get script takes steps in total
        input:
            script: command name
            act: the type of command
            model: mode setting
        """
        try:
            totalSteps = 0
            if (script == "gs_preinstall"):
                if model:
                    totalSteps = ClusterCommand.TOTAL_STEPS_PREINSTALL_L
                else:
                    totalSteps = ClusterCommand.TOTAL_STEPS_PREINSTALL
            elif (script == "gs_install"):
                if (model == ClusterCommand.INSTALL_STEP_CONFIG):
                    totalSteps = ClusterCommand.TOTAL_STEPS_INSTALL - 1
                elif (model == ClusterCommand.INSTALL_STEP_START):
                    totalSteps = ClusterCommand.TOTAL_STEPS_INSTALL - 2
                else:
                    totalSteps = ClusterCommand.TOTAL_STEPS_INSTALL
            elif (script == "gs_om"):
                if (act == "managecn"):
                    if (model == "add"):
                        totalSteps = ClusterCommand.TOTAL_STEPS_OM_ADD
                    if (model == "delete"):
                        totalSteps = ClusterCommand.TOTAL_STEPS_OM_DELETE
                if (act == "changeip"):
                    totalSteps = ClusterCommand.TOTAL_STEPS_OM_CHANGEIP
            elif (script == "gs_sshexkey"):
                if model:
                    totalSteps = ClusterCommand.TOTAL_STEPS_SSHEXKEY - 2
                else:
                    totalSteps = ClusterCommand.TOTAL_STEPS_SSHEXKEY
            elif (script == "gs_shrink"):
                if (act == "entry1"):
                    totalSteps = ClusterCommand.TOTAL_STEPS_SHRINK_FIRST
                if (act == "entry2"):
                    totalSteps = ClusterCommand.TOTAL_STEPS_SHRINK_SECOND
                if (act == "entry3"):
                    totalSteps = ClusterCommand.TOTAL_STEPS_SHRINK_THIRD
            elif (script in ["gs_replace", "gs_upgradectl", "gs_expand", "gs_uninstall", "gs_postuninstall"]):
                totalSteps = ClusterCommand.countTotalSteps2(script, act, model)

            return totalSteps
        except Exception as e:
            raise Exception(str(e))

    @staticmethod
    def countTotalSteps2(script, act, model):
        """
        function: get the total steps for gs_replace,gs_upgradectl,gs_expand,gs_uninstall,gs_postuninstall.
        input: string,string,string
        output: int.
        """
        totalSteps = 0
        if (script == "gs_replace"):
            if (act == "warm-standby"):
                if (model == ClusterCommand.WARM_STEP_INIT):
                    totalSteps = ClusterCommand.TOTAL_STEPS_REPLACE_WARM_STANDBY
                if (model == ClusterCommand.WARM_STEP_REPLACEIPS):
                    totalSteps = ClusterCommand.TOTAL_STEPS_REPLACE_WARM_STANDBY_REPLACE
                if (model == ClusterCommand.WARM_STEP_INSTALL):
                    totalSteps = ClusterCommand.TOTAL_STEPS_REPLACE_WARM_STANDBY_INSTALL
                if (model == ClusterCommand.WARM_STEP_CONFIG):
                    totalSteps = ClusterCommand.TOTAL_STEPS_REPLACE_WARM_STANDBY_CONFIG
            if (act == "install"):
                totalSteps = ClusterCommand.TOTAL_STEPS_REPLACE_INSTALL
            if (act == "config"):
                totalSteps = ClusterCommand.TOTAL_STEPS_REPLACE_CONFIG
            if (act == "start"):
                totalSteps = ClusterCommand.TOTAL_STEPS_REPLACE_START
        elif (script == "gs_upgradectl"):
            if (act == "auto-upgrade"):
                if (model == "binary-upgrade"):
                    totalSteps = ClusterCommand.TOTAL_STEPS_BINARY_BUPGRADECTL
            if (act == "binary-upgrade"):
                totalSteps = ClusterCommand.TOTAL_STEPS_BINARY_BUPGRADECTL
        elif (script == "gs_expand"):
            if (act == "dilatation"):
                totalSteps = ClusterCommand.TOTAL_STEPS_EXPAND_DILA
            if (act == "redistribute"):
                totalSteps = ClusterCommand.TOTAL_STEPS_EXPAND_REDIS
        elif (script == "gs_uninstall"):
            totalSteps = ClusterCommand.TOTAL_STEPS_UNINSTALL
        elif (script == "gs_postuninstall"):
            totalSteps = ClusterCommand.TOTAL_STEPS_POSTUNINSTALL
        return totalSteps

    @staticmethod
    def check_input(jsonFilePath):
        """
        function: check the input, and load the backup JSON file.
        @param: N/A.
        @return: return [OK, para], if the backup JSON file is loaded successfully.
        """
        try:
            para = g_file.parseJsonFile(jsonFilePath)
            return [0, para]
        except TypeError as err:
            ERR_MSG = "input para is not json_string. %s" % err
            return [1, ERR_MSG]

    @staticmethod
    def executeSQLOnRemoteHost(hostName, port, sql, outputfile, database="postgres", action=""):
        """
        function: execute SQL on remote host
        input :hostName, port, sql, outputfile, database
        output: NA
        """
        try:
            from gspylib.threads.SshTool import SshTool
            from gspylib.common.OMCommand import OMCommand
            hosts = [hostName]
            gs_sshTool = SshTool(hosts, action)
            currentTime = datetime.utcnow().strftime("%Y-%m-%d_%H%M%S%f")
            pid = os.getpid()
            sqlfile = "%s_%s_%s.sql" % (hostName, pid, currentTime)
            tmpDir = DefaultValue.getTmpDirFromEnv() + "/"
            sqlfilepath = os.path.join(tmpDir, sqlfile)
            g_file.write_file_with_default_permission(sqlfilepath, sql, False)
            g_OSlib.scpFile(hostName, sqlfilepath, tmpDir)
            cmd = "%s  -p %s -S %s -f %s -d %s" % \
                  (OMCommand.getLocalScript("Local_Execute_Sql"),
                   port, sqlfilepath, outputfile, database)
            gs_sshTool.executeCommand(cmd, "execute SQL on remote host")
            cmd = "%s %s" % (g_Platform.getRemoveCmd("directory"), sqlfilepath)
            subprocess.getstatusoutput(cmd)
        except Exception as e:
            raise Exception(str(e))

    @staticmethod
    def excuteSqlOnLocalhost(port, sql, database="postgres"):
        """
        function: write output message
        input : sql
        output: NA
        """
        tmpresult = None
        conn = None
        libc = None
        try:
            from gspylib.common.SqlResult import sqlResult
            libpath = os.path.join(DefaultValue.getEnv("GAUSSHOME"), "lib")
            libpq = os.path.join(libpath, "libpq.so.5.5")
            libc = cdll.LoadLibrary(libpq)
            conn_opts = "dbname = '%s' application_name = 'OM' options='-c xc_maintenance_mode=on'  port = %s " % \
                        (database, port)
            err_output = ""
            libc.PQconnectdb.argtypes = [c_char_p]
            libc.PQconnectdb.restype = c_void_p
            libc.PQclear.argtypes = [c_void_p]
            libc.PQfinish.argtypes = [c_void_p]
            libc.PQerrorMessage.argtypes = [c_void_p]
            libc.PQerrorMessage.restype = c_char_p
            libc.PQresultStatus.argtypes = [c_void_p]
            libc.PQresultStatus.restype = c_int
            libc.PQexec.argtypes = [c_void_p, c_char_p]
            libc.PQexec.restype = c_void_p
            conn = libc.PQconnectdb(conn_opts.encode('utf-8'))
            if (conn is None):
                raise Exception("Failed to get connection with database by options: %s" % conn_opts)
            libc.PQstatus.argtypes = [c_void_p]
            if (libc.PQstatus(conn) != 0):
                raise Exception("Failed to get connection with database")
            tmpresult = libc.PQexec(conn, sql.encode('utf-8'))
            if (tmpresult is None):
                raise Exception("Can not get correct result by executing sql: %s" % sql)
            status = libc.PQresultStatus(tmpresult)

            resultObj = sqlResult(tmpresult)
            resultObj.parseResult()
            Error = libc.PQerrorMessage(conn)
            if (Error is not None):
                err_output = string_at(Error)
            result = resultObj.resSet
            output = err_output.decode()
            libc.PQclear(tmpresult)
            libc.PQfinish(conn)
            return status, result, output
        except Exception as e:
            libc.PQclear.argtypes = [c_void_p]
            libc.PQfinish.argtypes = [c_void_p]
            if tmpresult:
                libc.PQclear(tmpresult)
            if conn:
                libc.PQfinish(conn)
            raise Exception(str(e))

    @staticmethod
    def getSQLResult(hostName, jsonFile):
        """
        function: get sql result from jsonFile
        input : hostName,jsonFile
        output: status, result, error_output
        """
        # copy json file from remote host
        tmpDir = DefaultValue.getTmpDirFromEnv() + "/"
        filepath = os.path.join(tmpDir, jsonFile)
        scpCmd = g_Platform.getRemoteCopyCmd(filepath, tmpDir, hostName, False, "directory")
        DefaultValue.execCommandLocally(scpCmd)
        # parse json file
        error_output = ""
        (ret, para) = ClusterCommand.check_input(filepath)
        if (ret != 0):
            raise Exception("Error: can not load result data ")

        if "status" not in para:
            raise Exception("Error: can not get sql execute status")
        else:
            status = para["status"]

        if "result" not in para:
            raise Exception("Error: sql execute failed")
        else:
            result = para["result"]
        if "error_output" in para:
            error_output = para["error_output"]

            # remove json file from remote host and localhost
        g_file.removeDirectory(filepath)

        remoteCmd = g_Platform.getSshCmd(hostName)
        cmd = "%s \"%s '%s'\"" % (remoteCmd, g_Platform.getRemoveCmd("directory"), filepath)
        DefaultValue.execCommandLocally(cmd)

        return status, result, error_output

    @staticmethod
    def getInstStatusByGsctl(instdir):
        """
        """
        cmd = "gs_ctl query -D %s|grep '\<db_state\>'| awk -F ':' '{print $2}'" % instdir
        (status, output) = subprocess.getstatusoutput(cmd)
        return status, output

    @staticmethod
    def checkInstStatusByGsctl(instdir, retryCount=100, isNormal=False):
        """
        function: check single instance status for local instance.
                Wait for 5 minutes. If the instance status is still Catchup,
                the instance status is Normal.
        input: NA
        output: (status, output)
        """
        count = 0
        while (count < retryCount):
            time.sleep(3)
            count += 1
            (status, output) = ClusterCommand.getInstStatusByGsctl(instdir)
            if (status == 0 and output.strip() == "Normal"):
                break
            elif (status == 0 and count == retryCount and not isNormal and output.strip() == "Catchup"):
                output = "Normal"
        return (status, output)

    @staticmethod
    def getOneNormalCNInstId(clusterInfo, normal_cn_nodes):
        """
        """
        cn_inst_id = 0
        for dbNode in clusterInfo.dbNodes:
            for cn_inst in dbNode.coordinators:
                if cn_inst.hostname in normal_cn_nodes:
                    cn_inst_id = cn_inst.instanceId
                    break
        if cn_inst_id == 0:
            raise Exception("ERROR: Can not found normal cn inst id from %s." % normal_cn_nodes)
        return cn_inst_id

    @staticmethod
    def doUpdateSqlOnCnNodes(clusterInfo, normal_cn_inst_id, user, normal_cn_nodes):
        """
        do the SQL command at all CN instance
        """
        query_list = []
        for dbNode in clusterInfo.dbNodes:
            # find a cn instance
            if len(dbNode.coordinators) == 0:
                continue
            # execute sql command only on a NormalCnNode
            if dbNode.name not in normal_cn_nodes:
                continue
            cn_inst = dbNode.coordinators[0]
            query_list.append("%s@%s@%s@%s" % (normal_cn_inst_id, cn_inst.hostname, cn_inst.port, user))

        if len(query_list):
            pool = ThreadPool(DefaultValue.getCpuSet())
            pool.map(ClusterCommand.doUpdateSqlOnOneCnNode, query_list)
            pool.close()
            pool.join()
        return True

    @staticmethod
    def doUpdateSqlOnOneCnNode(query_str):
        """
        """
        retryCounts = 20
        normal_cn_inst_id = query_str.split('@')[0]
        sql = "UPDATE pg_catalog.pg_jobs " \
              "SET job_node=(SELECT oid FROM pg_catalog.pgxc_node WHERE node_name='cn_%s') " \
              "WHERE job_node NOT IN (SELECT distinct(oid) FROM pg_catalog.pgxc_node);" % str(normal_cn_inst_id)
        host = query_str.split('@')[1]
        port = int(query_str.split('@')[2])
        user = query_str.split('@')[3]
        for _ in range(0, retryCounts):
            status, output = ClusterCommand.remoteSQLCommand(sql,
                                                             user,
                                                             host,
                                                             port,
                                                             False,
                                                             DefaultValue.DEFAULT_DB_NAME,
                                                             is_inplace_upgrade=True)
            if status == 0:
                return
            time.sleep(3)
        raise Exception(ErrorCode.GAUSS_513["GAUSS_51300"] % sql + " Error:\n%s" % output)

    @staticmethod
    def updateJobData(normal_cn_inst_id, clusterInfo, user, normal_cn_nodes):
        """
        """
        status = ClusterCommand.doUpdateSqlOnCnNodes(clusterInfo, normal_cn_inst_id, user, normal_cn_nodes)
        if status:
            return 0
        else:
            return 1

    @staticmethod
    def createSqlConnectOnLocalhost(port, database="postgres"):
        """
        function: create sql connect on localhost
        input : port
        output: conn
        """
        conn = None
        libc = None
        try:
            libc = ClusterCommand.loadAndInitLibc()
            conn_opts = "dbname = '%s' application_name = 'OM' port = %s " % \
                        (database, port)
            conn = libc.PQconnectdb(conn_opts.encode('utf-8'))
            if conn is None:
                raise Exception("Failed to get connection with database by options: %s" % conn_opts)
            if libc.PQstatus(conn) != 0:
                raise Exception("Failed to get connection with database")
            return conn
        except Exception as err:
            if conn:
                libc.PQfinish(conn)
            raise Exception(str(err))

    @staticmethod
    def executeSqlWithConn(sql, conn):
        """
        function: execute sql with connect
        input : sql
        output: NA
        """
        tmpresult = None
        libc = None
        try:
            from gspylib.common.SqlResult import sqlResult
            libc = ClusterCommand.loadAndInitLibc()
            err_output = ""
            if conn is None or libc.PQstatus(conn) != 0:
                raise Exception("Failed to get connection with database")
            tmpresult = libc.PQexec(conn, sql.encode('utf-8'))
            if tmpresult is None:
                raise Exception("Can not get correct result by executing sql: %s" % sql)
            status = libc.PQresultStatus(tmpresult)

            resultObj = sqlResult(tmpresult)
            resultObj.parseResult()
            Error = libc.PQerrorMessage(conn)
            if Error is not None:
                err_output = string_at(Error)
            result = resultObj.resSet
            output = err_output.decode()
            libc.PQclear(tmpresult)
            return status, result, output
        except Exception as err:
            if tmpresult:
                libc.PQclear(tmpresult)
            raise Exception(str(err))

    @staticmethod
    def finishConnOnLocalhost(conn):
        """
        function: finish connect on localhost
        input : conn
        output: NA
        """
        libc = None
        try:
            libc = ClusterCommand.loadAndInitLibc()
            if conn is None:
                return
            libc.PQfinish(conn)
            return
        except Exception as err:
            if conn:
                libc.PQfinish(conn)
            raise Exception(str(err))

    @staticmethod
    def loadAndInitLibc():
        """
        function: load and init Libc
        input : NA
        output: NA
        """
        if ClusterCommand.libc is not None:
            return ClusterCommand.libc
        libpath = os.path.join(DefaultValue.getEnv("GAUSSHOME"), "lib")
        libpq = os.path.join(libpath, "libpq.so.5.5")
        ClusterCommand.libc = cdll.LoadLibrary(libpq)
        ClusterCommand.libc.PQconnectdb.argtypes = [c_char_p]
        ClusterCommand.libc.PQconnectdb.restype = c_void_p
        ClusterCommand.libc.PQclear.argtypes = [c_void_p]
        ClusterCommand.libc.PQfinish.argtypes = [c_void_p]
        ClusterCommand.libc.PQerrorMessage.argtypes = [c_void_p]
        ClusterCommand.libc.PQerrorMessage.restype = c_char_p
        ClusterCommand.libc.PQresultStatus.argtypes = [c_void_p]
        ClusterCommand.libc.PQresultStatus.restype = c_int
        ClusterCommand.libc.PQexec.argtypes = [c_void_p, c_char_p]
        ClusterCommand.libc.PQexec.restype = c_void_p
        ClusterCommand.libc.PQstatus.argtypes = [c_void_p]
        return ClusterCommand.libc
