#!/usr/bin/env python3
# -*- coding:utf-8 -*-
try:
    import sys
    import os
    import subprocess
    import time
    from datetime import datetime, timedelta

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

KEY = "etcd"


class ETCD_OLAP(BaseComponent):
    """
    Gauss 200 etcd component class
    """

    def __init__(self, logger=None):
        """
        function : init function
        input  : logger class
        output : NA
        """
        super(ETCD_OLAP, self).__init__()
        self.etcdKeyFile = "etcd.key"
        self.etcdCrtFile = "etcd.crt"
        self.CAPath = ""
        self.sslConfigFile = ""
        self.sshTool = None
        self.logger = logger
        self.CAPath = "%s/demoCA" % DefaultValue.getTmpDirFromEnv()

    def getEtcdUrl(self, user, Nodeslist):
        """
        """
        etcdhost = []
        gaussHome = DefaultValue.getInstallDir(user)
        crtPath = "%s/share/sslcert/etcd/etcdca.crt" % gaussHome
        clientkeyPath = "%s/share/sslcert/etcd/client.key" % gaussHome
        clientcrtPath = "%s/share/sslcert/etcd/client.crt" % gaussHome
        cmdpre = "etcdctl --cacert=%s --cert=%s --key=%s --endpoints=" % \
                 (crtPath, clientcrtPath, clientkeyPath)
        tmpcmdpre = cmdpre
        for node in Nodeslist:
            if (len(node.etcds) > 0):
                etcdhost.append(node.name)
                etcdinst = node.etcds[0]
                cmdpre += "https://%s:%s," % (
                    etcdinst.listenIps[0], etcdinst.port)

        if (cmdpre == tmpcmdpre):
            return "", []

        if (cmdpre != tmpcmdpre):
            cmdpre = cmdpre[:-1]
        self.logger.debug("The etcdctl cmdpre:%s" % cmdpre)
        return cmdpre, etcdhost

    def etcdHealth(self, user, check_all, Nodelist, warmStandbyList, OldNodes):
        """
        function : check etcd health
        input  : user, check_all, Nodelist, warmStandbyList, OldNodes
        output : True or False
        """
        try:
            # get etcd ca.crt path
            gaussHome = DefaultValue.getInstallDir(user)
            crtPath = "%s/share/sslcert/etcd/etcdca.crt" % gaussHome
            clientkeyPath = "%s/share/sslcert/etcd/client.key" % gaussHome
            clientcrtPath = "%s/share/sslcert/etcd/client.crt" % gaussHome
            cmdpre = "etcdctl --cacert=%s --cert=%s --key=%s --endpoints=" % (crtPath, clientcrtPath, clientkeyPath)
            etcdnum = etcdnormalcount = 0
            etcdclusterhealth = True
            warmnodeip = []
            for node in Nodelist:
                if len(node.etcds) > 0 and (node.name not in warmStandbyList):
                    etcdinst = node.etcds[0]
                    cmdpre += "https://%s:%s," % (etcdinst.listenIps[0], etcdinst.port)
                    etcdnum += len(node.etcds)
                    if len(OldNodes) > 0:
                        for onode in OldNodes:
                            if node.name == onode.name:
                                warmnodeip.append(etcdinst.listenIps[0])
            self.logger.debug("The ETCD warmnodeip:%s\n The ETCD number:%s" % (warmnodeip, etcdnum))
            if etcdnum > 0:
                cmd = "%s endpoint health" % cmdpre[:-1]
                self.logger.debug("Command for check the ETCD health:%s" % cmd)
                check_times = 0
                while True:
                    (_, output) = subprocess.getstatusoutput(cmd)
                    self.logger.debug("The ETCD healthy:%s" % output)

                    for line in output.strip().split('\n'):
                        if line.find("is healthy:") >= 0:
                            for listeenip in warmnodeip:
                                if line.find(listeenip) >= 0:
                                    flag = 1

                                    if flag == 0:
                                        etcdnormalcount += 1
                        if line.find("unhealthy") >= 0:
                            etcdclusterhealth = False

                    self.logger.debug("The number of normal ETCD:%s" % etcdnormalcount)
                    if etcdclusterhealth:
                        self.logger.debug("ETCD cluster is healthy")
                    else:
                        self.logger.debug("ETCD cluster is unhealthy")
                    if etcdnormalcount <= float(etcdnum // 2) and not etcdclusterhealth:
                        if check_times >= 12:
                            raise Exception(ErrorCode.GAUSS_527["GAUSS_52703"])
                        time.sleep(5)
                        check_times += 1
                    else:
                        break
        except Exception as e:
            raise Exception(str(e))
        return etcdclusterhealth

    def startEtcd(self, user):
        """
        function : start etcd instamce
        input  : user
        output : NA
        """
        self.logger.debug("Starting etcds")
        try:
            gaussHome = DefaultValue.getInstallDir(user)
            crtPath = "%s/share/sslcert/etcd/etcdca.crt" % gaussHome
            clientkeyPath = "%s/share/sslcert/etcd/client.key" % gaussHome
            clientcrtPath = "%s/share/sslcert/etcd/client.crt" % gaussHome
            if ([] != self.instInfo.listenIps):
                self.logger.debug("Waiting for ETCD healthy.")
                cmd = "etcdctl --endpoints=https://%s:%s --cacert=%s --cert=%s --key=%s endpoint health" % (
                    self.instInfo.listenIps[0], self.instInfo.port, crtPath, clientcrtPath, clientkeyPath)
                self.logger.debug("Command for check ETCD status: %s" % cmd)
                i = 0
                while (i < 31):
                    if (i >= 30):
                        raise Exception(
                            ErrorCode.GAUSS_516["GAUSS_51607"] % "etcd service")
                    (status, output) = subprocess.getstatusoutput(cmd)
                    if (status == 0 and output.find("unhealthy") < 0):
                        self.logger.debug("ETCD status has been healthy.")
                        break
                    else:
                        i = i + 1
                        time.sleep(1)
        except Exception as e:
            raise Exception(str(e))

    def stopEtcd(self, user):
        """
        function : Stop etcd instamce
        input  : user
        output : NA
        """
        try:
            gaussHome = DefaultValue.getInstallDir(user)
            self.logger.debug("Stopping ETCD instance.")
            etcd_manual_start_file = "%s/bin/etcd_manual_start" % gaussHome
            g_file.createFile(etcd_manual_start_file)

            EndTime = datetime.now() + timedelta(seconds=180)
            # wait and check etcd instance is stopped
            Running = True
            while Running:
                result = []
                etcdprocess = "%s/bin/etcd" % gaussHome
                result = g_OSlib.getProcess(etcdprocess)
                if (len(result) == 0):
                    Running = False

                if (datetime.now() > EndTime and Running):
                    raise Exception(
                        ErrorCode.GAUSS_516["GAUSS_51610"] % "ETCD instance")
                time.sleep(5)

            self.logger.debug("Successfully stopped ETCD instance.")
        except Exception as e:
            raise Exception(str(e))

    def addEtcd(self, user, addlist, Nodeslist):
        """
        function : Add nodes to etcd list
        input  : user, addlist, Nodeslist
        output : NA
        """
        try:
            # get GAUSSHOME path

            cmdpre, _ = self.getEtcdUrl(user, Nodeslist)
            if (cmdpre == ""):
                return

            self.logger.debug("Deleting ETCD.")
            for addnode in addlist:
                # add new etcd instance
                newinst = addnode.etcds[0]
                addcmd = "%s member add etcd_%s --peer-urls=http://%s:%s" % (
                    cmdpre, newinst.instanceId, newinst.haIps[0], newinst.haPort)
                self.logger.debug("Command for add ETCD: %s" % addcmd)
                add_times = 0
                while True:
                    (status, output) = subprocess.getstatusoutput(addcmd)
                    if (status != 0):
                        if (add_times >= 12):
                            raise Exception(
                                ErrorCode.GAUSS_527["GAUSS_52700"] + " Error: \n%s" % output)
                        time.sleep(5)
                        add_times += 1
                    else:
                        break
        except Exception as e:
            raise Exception(str(e))

    def deleteEtcd(self, user, deletelist, Nodeslist):
        """
        function : remove old etcd instance from etcd list
        input  : user, addlist, Nodeslist
        output : NA
        """
        try:
            cmdpre, _ = self.getEtcdUrl(user, Nodeslist)
            if (cmdpre == ""):
                return

            self.logger.debug("Deleting ETCD.")
            for deletenode in deletelist:
                cmd = "%s member list | grep %s" % (
                    cmdpre, deletenode.etcds[0].haIps[0])
                self.logger.debug("Command for obtaining ETCD key: %s" % cmd)
                (status, output) = subprocess.getstatusoutput(cmd)
                if (status != 0 or output.strip() == ""):
                    self.logger.debug("The old haip of etcd_%s is not contain in member list. Output:\n%s" % (
                        deletenode.instanceId, output))
                    continue
                else:
                    etcdkey = output.split(
                        "\n")[-1].strip().split(",")[0].strip().split("[")[0].strip()

                # remove old etcd instance
                removecmd = "%s member remove %s" % (cmdpre, etcdkey)
                self.logger.debug("Command for delete ETCD: %s" % removecmd)
                remove_times = 0
                while True:
                    (status, output) = subprocess.getstatusoutput(removecmd)
                    if (status != 0):
                        if (remove_times >= 12):
                            raise Exception(
                                ErrorCode.GAUSS_527["GAUSS_52700"] + " Error: \n%s" % output)
                        time.sleep(5)
                        remove_times += 1
                    else:
                        break
        except Exception as e:
            raise Exception(str(e))

    def getEtcd(self, user, Nodeslist):
        """
        function : Get ETCD info list
        input  : user, Nodeslist
        output : etcdInfo
        """
        try:
            etcdInfo = []
            cmdpre, _ = self.getEtcdUrl(user, Nodeslist)
            if (cmdpre == ""):
                return etcdInfo

            # Get ETCD info list
            cmd = "%s member list" % cmdpre
            (status, output) = subprocess.getstatusoutput(cmd)
            if (status != 0 or output.strip() == ""):
                raise Exception(
                    ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " Error: \n%s " % output)
            else:
                linelst = output.split("\n")
                etcdInfo = linelst[1:]

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

    def cleanFileDir(self):
        """
        function : clean tmp CA path (Class internal call)
        input  : NA
        output : NA
        """
        try:
            if (os.path.exists(self.CAPath)):
                g_file.removeDirectory(self.CAPath)
        except Exception as e:
            self.logger.debug("Failed to clean CA certification center.")
            raise Exception(str(e))

    def createCACenter(self, user, RootCrtFile="", RootKeyFile=""):
        """
        function : Creat CA certification center (Class internal call)
        input  : user, RootCrtFile, RootKeyFile
        output : NA
        """
        self.logger.debug("Creating CA certification center.")
        try:
            self.logger.debug("Generating a CA root certificate.")
            # Get openssl.cnf under $GAUSSHOME path
            gaussHome = DefaultValue.getInstallDir(user)
            self.sslConfigFile = "%s/share/sslcert/etcd/openssl.cnf" % gaussHome
            # 1.clean file
            self.cleanFileDir()
            # 2.Create paths and files
            g_file.createDirectory(
                self.CAPath, True, DefaultValue.KEY_DIRECTORY_MODE)
            cmd = g_Platform.getCdCmd(self.CAPath)
            (status, output) = subprocess.getstatusoutput(cmd)
            if (status != 0):
                raise Exception(
                    ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " Error: \n%s " % output)
            privateDir = "%s/private" % self.CAPath
            certsDir = "%s/certs" % self.CAPath
            newcertsDir = "%s/newcerts" % self.CAPath
            g_file.createDirectory(privateDir)
            g_file.createDirectory(certsDir)
            g_file.createDirectory(newcertsDir)
            indexFile = "%s/index.txt" % self.CAPath
            serialFile = "%s/serial" % self.CAPath
            g_file.createFile(indexFile, False)
            g_file.createFile(serialFile)
            serialnumber = ["00"]
            g_file.writeFile(serialFile, serialnumber, 'w')

            # Copy root cert and key file
            if (os.path.exists(RootKeyFile) and os.path.exists(RootCrtFile)):
                g_file.cpFile(RootKeyFile, privateDir)
                g_file.cpFile(RootCrtFile, certsDir)
        except Exception as e:
            self.logger.debug("Failed to create CA certification center.")
            raise Exception(str(e))
        self.logger.debug("Successful create CA certification center.")

    def generateRootCert(self, user):
        """
        function: Generating CA root certificate
        input : NA
        output: NA
        """
        self.logger.debug("Generating CA root certificate")
        try:
            gaussHome = DefaultValue.getInstallDir(user)
            etcddir = "%s/share/sslcert/etcd/" % gaussHome
            rootCert = "%s/certs/etcdca.crt" % self.CAPath
            rootCsr = "%s/certs/ca.csr" % self.CAPath
            rootKey = "%s/private/ca.key" % self.CAPath
            # Create CA certification center
            self.createCACenter(user)
            # Generate a CA root certificate and sign it
            cmd = g_Platform.getCdCmd(self.CAPath)
            cmd += " && export LD_LIBRARY_PATH=$GAUSSHOME/lib:$LD_LIBRARY_PATH"
            cmd += " && openssl req -config '%s' -new -nodes -keyout '%s' -out '%s' -subj '/CN=cn'" % (
                self.sslConfigFile, rootKey, rootCsr)
            cmd += " && openssl ca -startdate 170101000000Z -config '%s' -extensions v3_ca " \
                   "-selfsign -batch -keyfile '%s' -out '%s' -infiles '%s'" % \
                   (self.sslConfigFile, rootKey, rootCert, rootCsr)
            (status, output) = subprocess.getstatusoutput(cmd)
            if (status != 0):
                raise Exception(
                    ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " Error: \n%s " % output)

            g_file.changeMode(DefaultValue.KEY_FILE_MODE, rootCert)
            g_file.changeMode(DefaultValue.KEY_FILE_MODE, rootKey)
            # Copy etcdca.crt etcd.key client.crt client.key to the ETCD directory
            g_file.cpFile(rootCert, etcddir)
            g_file.cpFile(rootKey, etcddir)
        except Exception as e:
            # clean file
            self.cleanFileDir()
            raise Exception(str(e))
        self.logger.debug("Successful generate CA root certificate.")

    def generateClientCert(self, user, CertName="", nodeIP="", instanceDir=""):
        """
        function: Generating client certificate
        input : string user, string certName,string nodeIP,string instanceDir
        output: NA
        """
        self.logger.debug("Generating client certificate.")
        try:
            if (CertName == ""):
                raise Exception(
                    ErrorCode.GAUSS_502["GAUSS_50203"] % "certificate name")
            # Get the openssl.cnf under $GAUSSHOME path
            gaussHome = DefaultValue.getInstallDir(user)
            etcddir = "%s/share/sslcert/etcd/" % gaussHome
            rootKey = "%s/ca.key" % etcddir
            rootCert = "%s/etcdca.crt" % etcddir
            clientkey = "%s/%s.key" % (self.CAPath, CertName)
            clientCert = "%s/%s.crt" % (self.CAPath, CertName)
            clientCsr = "%s/%s.csr" % (self.CAPath, CertName)

            if ((not os.path.exists(rootKey)) or (not os.path.exists(rootCert))):
                raise Exception(
                    ErrorCode.GAUSS_502["GAUSS_50201"] % rootCert + " or " + rootKey)
            # Create CA certification center
            self.createCACenter(user, rootCert, rootKey)

            rootCert = "%s/certs/etcdca.crt" % self.CAPath
            rootKey = "%s/private/ca.key" % self.CAPath
            # Issue a certificate
            cmd = g_Platform.getCdCmd(self.CAPath)
            if (nodeIP != ""):
                # We must set the environment variable SAN to specify the ETCD certificate field IP Address.
                cmd += " && export SAN=\"IP:%s\"" % nodeIP
            cmd += " && export LD_LIBRARY_PATH=$GAUSSHOME/lib:$LD_LIBRARY_PATH"
            cmd += " && openssl req -config '%s' -new -nodes -keyout '%s' -out '%s' -subj '/CN=cn'" % (
                self.sslConfigFile, clientkey, clientCsr)
            cmd += " && openssl ca -startdate 170101000000Z -config '%s' -extensions etcd_server " \
                   "-batch -keyfile '%s' -cert '%s' -out '%s' -infiles '%s'" % \
                   (self.sslConfigFile, rootKey, rootCert, clientCert, clientCsr)
            (status, output) = subprocess.getstatusoutput(cmd)
            if (status != 0):
                raise Exception(
                    ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " Error: \n%s " % output)

            g_file.changeMode(DefaultValue.KEY_FILE_MODE, clientkey)
            g_file.changeMode(DefaultValue.KEY_FILE_MODE, clientCert)
            # Copy client.crt client.key to the ETCD directory
            if (instanceDir != ""):
                etcddir = instanceDir
            g_file.cpFile(clientkey, etcddir)
            g_file.cpFile(clientCert, etcddir)
            cmd = "unset SAN"
            (status, output) = subprocess.getstatusoutput(cmd)
            if (status != 0):
                raise Exception(
                    ErrorCode.GAUSS_514["GAUSS_51400"] % cmd + " Error: \n%s " % output)
            # Clean file
            self.cleanFileDir()
        except Exception as e:
            # Clean file
            self.cleanFileDir()
            raise Exception(str(e))
        self.logger.debug("Successful generat client certificate.")

    def generateKeys(self, user, hostList):
        """
        function: Generating key (cluster install call)
        input : user, hostList
        output: NA
        """
        try:
            if self.sshTool is None:
                self.sshTool = SshTool(hostList, KEY)
            gaussHome = DefaultValue.getInstallDir(user)
            etcddir = "%s/share/sslcert/etcd/" % gaussHome
            rootKey = "%s/ca.key" % etcddir
            rootCert = "%s/etcdca.crt" % etcddir
            clientCert = "%s/client.key" % etcddir
            clientKey = "%s/client.crt" % etcddir
            if ((not os.path.exists(rootCert)) or (not os.path.exists(rootKey))):
                self.generateRootCert(user)
                # Distribute the file rootCert and rootKey to all nodes
                self.sshTool.scpFiles(rootCert, etcddir, hostList)
                self.sshTool.scpFiles(rootKey, etcddir, hostList)
            if ((not os.path.exists(clientCert)) or (not os.path.exists(clientKey))):
                self.generateClientCert(user, "client")
                # Distribute the file clientCert and clientKey to all nodes
                self.sshTool.scpFiles(clientCert, etcddir, hostList)
                self.sshTool.scpFiles(clientKey, etcddir, hostList)
        except Exception as e:
            raise Exception(str(e))

    def initInstance(self):
        """
        function: init instance
        input : user
        output: NA
        """
        try:
            user = g_OSlib.getUserInfo()["name"]
            gaussHome = DefaultValue.getInstallDir(user)
            etcddir = "%s/share/sslcert/etcd/" % gaussHome
            rootCert = "%s/ca.key" % etcddir
            rootKey = "%s/etcdca.crt" % etcddir
            # Generate local node certificate and keyfile
            if (os.path.exists(rootCert) or os.path.exists(rootKey)):
                self.generateClientCert(user, "etcd", ",IP:".join(
                    [ip for ip in self.instInfo.listenIps]), self.instInfo.datadir)
            else:
                raise Exception(
                    ErrorCode.GAUSS_502["GAUSS_50201"] % rootCert + " or " + rootKey)
        except Exception as e:
            raise Exception(str(e))

    def uninstall(self):
        """
        function: uninstall etcd instance
        input : NA
        output: NA
        """
        try:
            user = g_OSlib.getUserInfo()["name"]
            gaussHome = DefaultValue.getInstallDir(user)
            processName = "%s/bin/etcd" % gaussHome
            DefaultValue.KillAllProcess(user, processName)
            datadir = self.instInfo.datadir
            if (os.path.exists(datadir)):
                g_file.cleanDirectoryContent(datadir)
        except Exception as e:
            raise Exception(str(e))
