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

try:
    import os
    import sys
    import subprocess

    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.gsnetwork import g_network
    from gspylib.common.common.default_value import DefaultValue
except ImportError as ie:
    sys.exit("[GAUSS-52200] : Unable to import module: %s." % str(ie))


class ClusterInstanceConfig():
    """
    Set Instance Config
    """

    def __init__(self):
        pass

    @staticmethod
    def getCMDict(user, configItemType=None, alarm_component=None):
        """
        function: Get CM configuration
        input : NA
        output: NA
        """
        tmpCMDict = {}
        tmpCMDict["cm_server_log_dir"] = "%s/cm/cm_server" % DefaultValue.getUserLogDirWithUser(user)
        tmpCMDict["cm_agent_log_dir"] = "%s/cm/cm_agent" % DefaultValue.getUserLogDirWithUser(user)
        if configItemType == "ConfigInstance":
            tmpCMDict["alarm_component"] = "%s" % alarm_component
            tmpCMDict["unix_socket_directory"] = "'%s'" % DefaultValue.getTmpDirFromEnv()
        return tmpCMDict

    @staticmethod
    def getCNDict(cooInst, user, configItemType=None):
        """
        function: Get CN configuration
        input : NA
        output: NA
        """
        tmpCNDict = {}
        tmpCNDict["listen_addresses"] = "'localhost,%s'" % ",".join(cooInst.listenIps)
        tmpCNDict["local_bind_address"] = "'%s'" % cooInst.listenIps[0]
        tmpCNDict["port"] = str(cooInst.port)
        tmpCNDict["comm_sctp_port"] = str(cooInst.sctpPort)
        tmpCNDict["comm_control_port"] = str(cooInst.controlPort)

        if configItemType != "ChangeIPUtility":
            tmpCNDict["pooler_port"] = str(cooInst.haPort)
            tmpCNDict["log_directory"] = "'%s/pg_log/cn_%d'" % \
                                         (DefaultValue.getUserLogDirWithUser(user), cooInst.instanceId)
            tmpCNDict["audit_directory"] = "'%s/pg_audit/cn_%d'" % \
                                           (DefaultValue.getUserLogDirWithUser(user), cooInst.instanceId)
        if configItemType != "ConfigInstance" and configItemType != "ChangeIPUtility":
            tmpCNDict["pgxc_node_name"] = "'cn_%d'" % cooInst.instanceId
        if len(cooInst.ssdDir) != 0 and configItemType != "ChangeIPUtility":
            tmpCNDict["ssd_cache_dir"] = "'%s'" % (cooInst.ssdDir)
            tmpCNDict["enable_adio_function"] = "on"
            tmpCNDict["enable_cstore_ssd_cache"] = "on"
        return tmpCNDict

    @staticmethod
    def get_azname(clusterInfo, peerInsts):
        """
        check az name  and git tmpDNDict
        :param clusterInfo:
        :param peerInsts:
        :return:tmpDNDict
        """
        tmpDNDict = {}
        azNames = clusterInfo.getazNames()
        if len(azNames) == 1 and len(peerInsts) <= 2:
            tmpDNDict["synchronous_standby_names"] = "'ANY 1(AZ1)'"
        elif len(azNames) == 1 and len(peerInsts) == 3:
            tmpDNDict["synchronous_standby_names"] = "'ANY 2(AZ1)'"
        elif len(azNames) == 2 and len(peerInsts) == 3:
            tmpDNDict["synchronous_standby_names"] = "'ANY 2(AZ1,AZ2)'"
        elif len(azNames) == 3 and len(peerInsts) == 3:
            tmpDNDict["synchronous_standby_names"] = "'ANY 2(AZ1,AZ2)'"
        elif len(azNames) == 3 and len(peerInsts) == 4:
            tmpDNDict["synchronous_standby_names"] = "'FIRST 2(AZ1,AZ2)'"
        elif len(azNames) == 3 and len(peerInsts) <= 7:
            tmpDNDict["synchronous_standby_names"] = "'FIRST 3(AZ1,AZ2)'"
        return tmpDNDict

    @staticmethod
    def getDNSDict(dnInst, user, configItemType=None, clusterInfo=None, peerInsts=None):
        """
        function: Get DN configuration
        input : NA
        output: NA
        """
        if peerInsts is None:
            peerInsts = []
        tmpDNDict = {"listen_addresses": "'%s'" % ",".join(dnInst.listenIps),
                     "local_bind_address": "'%s'" % dnInst.listenIps[0],
                     "port": dnInst.port,
                     "comm_sctp_port": str(dnInst.sctpPort),
                     "comm_control_port": str(dnInst.controlPort)}

        if configItemType != "ChangeIPUtility":
            tmpDNDict["log_directory"] = "'%s/pg_log/dn_%d'" % \
                                         (DefaultValue.getUserLogDirWithUser(user), dnInst.instanceId)
            tmpDNDict["audit_directory"] = "'%s/pg_audit/dn_%d'" % \
                                           (DefaultValue.getUserLogDirWithUser(user), dnInst.instanceId)
        if (clusterInfo is not None and
                (clusterInfo.isSinglePrimaryMultiStandbyCluster() or
                 clusterInfo.isSingleInstCluster())):
            tmpDNDict["application_name"] = "'dn_%s'" % dnInst.instanceId
            tmpDict = ClusterInstanceConfig.get_azname(clusterInfo, peerInsts)
            tmpDNDict.update(tmpDict)

        if len(dnInst.ssdDir) != 0 and configItemType != "ChangeIPUtility":
            tmpDNDict["ssd_cache_dir"] = "'%s'" % dnInst.ssdDir
            tmpDNDict["enable_adio_function"] = "on"
            tmpDNDict["enable_cstore_ssd_cache"] = "on"

        if clusterInfo is not None and configItemType != "ChangeIPUtility":
            cpu_list = os.path.join(clusterInfo.appPath, "etc", "cpulist")
            if os.path.exists(cpu_list):
                with open(cpu_list, "r") as fp:
                    cpu_list_info = fp.read().splitlines()
                db_node = clusterInfo.getDbNodeByName(dnInst.hostname)
                if db_node is None:
                    numa_bind_node = "'none'"
                else:
                    numa_bind_node = DefaultValue.get_numa_bind_node_value(db_node, dnInst, cpu_list_info)
            else:
                numa_bind_node = "'none'"

            tmpDNDict["numa_bind_node"] = numa_bind_node

        return tmpDNDict

    @staticmethod
    def setGtmInfo(gtmList):
        """
        function: Modify GTM info
        input : g_dbNodes
        output: tmpDict
        """
        masterGTM = None
        standbyGTM = []

        for gtmInst in gtmList:
            if gtmInst.instanceType == DefaultValue.MASTER_INSTANCE:
                masterGTM = gtmInst
            else:
                standbyGTM.append(gtmInst)

        tmpDict = {"gtm_host": "'%s'" % ",".join(masterGTM.listenIps), "gtm_port": masterGTM.port}
        for i in range(len(standbyGTM)):
            gtm_host = "gtm_host%d" % (i + 1)
            gtm_port = "gtm_port%d" % (i + 1)
            tmpDict[gtm_host] = "'%s'" % ",".join(standbyGTM[i].listenIps)
            tmpDict[gtm_port] = standbyGTM[i].port

        return tmpDict

    @staticmethod
    def setConfigItem(typename, datadir, configFile, parmeterDict):
        """
        function: Modify a parameter
        input : typename, datadir, configFile, parmeterDict
        output: NA
        """
        # check mpprc file path
        mpprc_file = DefaultValue.getMpprcFile()

        # comment out any existing entries for this setting
        if typename == DefaultValue.INSTANCE_ROLE_CMSERVER or typename == DefaultValue.INSTANCE_ROLE_CMAGENT:
            # gs_guc only support for gtm, CN and DN instance
            # if the type is cm_server or cm_agent, we will use sed to instead of it
            for entry in parmeterDict.items():
                key = entry[0]
                value = entry[1]
                # delete the old parameter information
                cmd = "sed -i 's/^.*\(%s.*=.*\)/#\\1/g' %s" % (key, configFile)
                (status, output) = subprocess.getstatusoutput(cmd)
                if status != 0:
                    raise Exception(ErrorCode.GAUSS_500["GAUSS_50008"] + " Error: \n%s" % output)

                # append new config to file
                cmd = 'echo "      " >> %s' % configFile
                (status, output) = subprocess.getstatusoutput(cmd)
                if status != 0:
                    raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " Error: \n%s" % output)

                cmd = 'echo "%s = %s" >> %s' % (key, value, configFile)
                (status, output) = subprocess.getstatusoutput(cmd)
                if status != 0:
                    raise Exception(ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " Error: \n%s" % output)
        else:
            # get instance type
            if typename == DefaultValue.INSTANCE_ROLE_GTM:
                inst_type = "gtm"
            elif typename == DefaultValue.INSTANCE_ROLE_DATANODE:
                inst_type = "datanode"
            elif typename == DefaultValue.INSTANCE_ROLE_COODINATOR:
                inst_type = "coordinator"
            else:
                raise Exception(ErrorCode.GAUSS_500["GAUSS_50009"] + " The parameter '-Z' value is incorrect.")
            # build GUC parameter string
            guc_str_list = []
            for entry in parmeterDict.items():
                guc_str_list.append(" -c \"%s=%s\"" % (entry[0], entry[1]))
            guc_str = "".join(guc_str_list)
            # check the GUC parameter string
            if guc_str == "":
                return
            cmd = "source %s; gs_guc set -Z %s -D %s %s" % (mpprc_file, inst_type, datadir, guc_str)
            DefaultValue.retry_gs_guc(cmd)

    @staticmethod
    def setReplConninfo(dbInst, peerInsts):
        """
        function: Modify replconninfo for datanode
        input : dbInst
        output: NA
        """
        if (len(peerInsts) != 1 and len(peerInsts) != 2):
            return "", "", None, ""
        (masterInst, standbyInst, dummyStandbyInst,
         nodename) = ClusterInstanceConfig.getRelationalInst(dbInst, peerInsts)
        connInfo1 = ""
        connInfo2 = ""
        channelCount = len(masterInst.haIps)
        for i in range(channelCount):
            if (dbInst.instanceType == DefaultValue.MASTER_INSTANCE):
                if (i > 0):
                    connInfo1 += ","
                connInfo1 += "localhost=%s localport=%d localservice=%d " \
                             "remotehost=%s remoteport=%d remoteservice=%d" \
                             % (dbInst.haIps[i], dbInst.haPort, dbInst.servicePort,
                                standbyInst.haIps[i], standbyInst.haPort, standbyInst.servicePort)
                if (dummyStandbyInst is not None):
                    if (i > 0):
                        connInfo2 += ","
                    connInfo2 += "localhost=%s localport=%d localservice=%d " \
                                 "remotehost=%s remoteport=%d remoteservice=%d" \
                                 % (dbInst.haIps[i], dbInst.haPort, dbInst.servicePort,
                                    dummyStandbyInst.haIps[i], dummyStandbyInst.haPort,
                                    dummyStandbyInst.servicePort)
            elif (dbInst.instanceType == DefaultValue.STANDBY_INSTANCE):
                if (i > 0):
                    connInfo1 += ","
                connInfo1 += "localhost=%s localport=%d localservice=%d " \
                             "remotehost=%s remoteport=%d remoteservice=%d" \
                             % (dbInst.haIps[i], dbInst.haPort, dbInst.servicePort,
                                masterInst.haIps[i], masterInst.haPort, masterInst.servicePort)
                if (dummyStandbyInst is not None):
                    if (i > 0):
                        connInfo2 += ","
                    connInfo2 += "localhost=%s localport=%d localservice=%d " \
                                 "remotehost=%s remoteport=%d remoteservice=%d" % \
                                 (dbInst.haIps[i], dbInst.haPort, dbInst.servicePort,
                                  dummyStandbyInst.haIps[i], dummyStandbyInst.haPort,
                                  dummyStandbyInst.servicePort)
            elif (dbInst.instanceType == DefaultValue.DUMMY_STANDBY_INSTANCE):
                if (i > 0):
                    connInfo1 += ","
                connInfo1 += "localhost=%s localport=%d localservice=%d " \
                             "remotehost=%s remoteport=%d remoteservice=%d" \
                             % (dbInst.haIps[i], dbInst.haPort, dbInst.servicePort,
                                masterInst.haIps[i], masterInst.haPort, masterInst.servicePort)
                if (i > 0):
                    connInfo2 += ","
                connInfo2 += "localhost=%s localport=%d localservice=%d " \
                             "remotehost=%s remoteport=%d remoteservice=%d" \
                             % (dbInst.haIps[i], dbInst.haPort, dbInst.servicePort,
                                standbyInst.haIps[i], standbyInst.haPort, standbyInst.servicePort)

        return connInfo1, connInfo2, dummyStandbyInst, nodename

    @staticmethod
    def getRelationalInst(dbInst, peerInsts):
        """
        function: get relational inst by dbInst
        input : dbInst, peerInsts
        output: masterInst, standbyInst, dummyStandbyInst, nodename
        """
        masterInst = None
        standbyInst = None
        dummyStandbyInst = None
        nodename = ""
        # init masterInst, standbyInst and dummyStandbyInst
        for i in range(len(peerInsts)):
            if (peerInsts[i].instanceType == DefaultValue.MASTER_INSTANCE):
                masterInst = peerInsts[i]
            elif (peerInsts[i].instanceType == DefaultValue.STANDBY_INSTANCE):
                standbyInst = peerInsts[i]
            elif (peerInsts[i].instanceType == DefaultValue.DUMMY_STANDBY_INSTANCE):
                dummyStandbyInst = peerInsts[i]

        if (dbInst.instanceType == DefaultValue.MASTER_INSTANCE):
            masterInst = dbInst
            nodename = "dn_%d_%d" % (masterInst.instanceId, standbyInst.instanceId)
        elif (dbInst.instanceType == DefaultValue.STANDBY_INSTANCE):
            standbyInst = dbInst
            nodename = "dn_%d_%d" % (masterInst.instanceId, standbyInst.instanceId)
        elif (dbInst.instanceType == DefaultValue.DUMMY_STANDBY_INSTANCE):
            dummyStandbyInst = dbInst
            nodename = "dn_%d_%d" % (masterInst.instanceId, dummyStandbyInst.instanceId)
        if (len(masterInst.haIps) == 0 or len(standbyInst.haIps) == 0):
            raise Exception(ErrorCode.GAUSS_516["GAUSS_51621"] + " Data directory: %s." % dbInst.datadir)
        if (dummyStandbyInst is not None and len(dummyStandbyInst.haIps) == 0):
            raise Exception(ErrorCode.GAUSS_516["GAUSS_51621"] + " Data directory: %s." % dbInst.datadir)

        return masterInst, standbyInst, dummyStandbyInst, nodename

    @staticmethod
    def getInstanceInfoForSinglePrimaryMultiStandbyCluster(dbInst, peerInsts):
        """
        function: get the instance name, master instance and standby instance list
        input : dbInst
        output: NA
        """
        masterInst = None
        standbyInstIdLst = []
        instance_name_list = []
        # init masterInst, standbyInst
        for i in range(len(peerInsts)):
            if peerInsts[i].instanceType == DefaultValue.MASTER_INSTANCE:
                masterInst = peerInsts[i]
            elif peerInsts[i].instanceType == DefaultValue.STANDBY_INSTANCE:
                standbyInstIdLst.append(peerInsts[i].instanceId)

        if dbInst.instanceType == DefaultValue.MASTER_INSTANCE:
            masterInst = dbInst
            instance_name_list = ["dn_%d" % masterInst.instanceId]
            standbyInstIdLst.sort()
            for i in range(len(standbyInstIdLst)):
                instance_name_list.append("_%d" % standbyInstIdLst[i])
        elif dbInst.instanceType == DefaultValue.STANDBY_INSTANCE:
            instance_name_list = ["dn_%d" % masterInst.instanceId]
            standbyInstIdLst.append(dbInst.instanceId)
            standbyInstIdLst.sort()
            for i in range(len(standbyInstIdLst)):
                instance_name_list.append("_%d" % standbyInstIdLst[i])
        instance_name = "".join(instance_name_list)
        return instance_name, masterInst, standbyInstIdLst

    @staticmethod
    def get_coordinator_channel_info(peer_insts, channel_count, db_inst):
        conn_info = []
        for j in range(len(peer_insts)):
            channel_info_list = []
            for i in range(channel_count):
                if i > 0:
                    channel_info_list.append(",")
                channel_info_list.append(("localhost=%s localport=%d localservice=%d "
                                          "remotehost=%s remoteport=%d remoteservice=%d" %
                                          (db_inst.haIps[i], db_inst.haPort, db_inst.servicePort,
                                           peer_insts[j].haIps[i], peer_insts[j].haPort,
                                           peer_insts[j].servicePort)))
            channel_info = "".join(channel_info_list)
            conn_info.append(channel_info)
        return conn_info

    @staticmethod
    def setReplConninfoForSinglePrimaryMultiStandbyCluster(dbInst, peerInsts):
        """
        function: Modify replconninfo for datanode
        input : dbInst
        output: NA
        """
        (nodename, masterInst, standbyInstIdLst) = \
            ClusterInstanceConfig.getInstanceInfoForSinglePrimaryMultiStandbyCluster(dbInst, peerInsts)
        if len(masterInst.haIps) == 0 or len(standbyInstIdLst) == 0:
            raise Exception(ErrorCode.GAUSS_516["GAUSS_51621"] +
                            " Data directory: %s." % dbInst.datadir)

        channelCount = len(masterInst.haIps)
        if dbInst.instanceType == DefaultValue.MASTER_INSTANCE:
            connInfo1 = ClusterInstanceConfig.get_coordinator_channel_info(peerInsts, channelCount, dbInst)
        else:
            connInfo1 = ClusterInstanceConfig.get_coordinator_channel_info(peerInsts, channelCount, dbInst)

        return connInfo1, nodename

    @staticmethod
    def getHbaItemForIP(ip, method):
        """
        function:
            Generate a pg_hba.conf item for specified IP.
            This function is available for IPv4 and IPv6 both.
        :param ip:
            IP string(NOT hostname)
        :param method:
            The authenticate method.
        :return:
            Item in string.
        """
        iptype = g_network.getIPType(ip)
        if 4 == iptype:
            item = "host    all    all    %s/32" % ip
        elif 6 == iptype:
            item = "host    all    all    %s/128" % g_network.formatIP(ip)
        else:
            raise Exception(ErrorCode.GAUSS_506["GAUSS_50603"] + (" IP string is %s" % ip))

        if method is not None and len(method) > 0:
            item = "%s    %s" % (item, method)

        return item
