#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2020 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under Mulan PSL v2.
# You can use this software according to the terms
# and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
#          http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
# ----------------------------------------------------------------------------
# Description  : gs_install is a utility to deploy a Gauss200 server.
#############################################################################

import os
import sys
package_path = os.path.dirname(os.path.realpath(__file__))
ld_path = package_path + "/gspylib/clib"
if 'LD_LIBRARY_PATH' not in os.environ:
    os.environ['LD_LIBRARY_PATH'] = ld_path
    os.execve(os.path.realpath(__file__), sys.argv, os.environ)
if not os.environ.get('LD_LIBRARY_PATH').startswith(ld_path):
    os.environ['LD_LIBRARY_PATH'] = \
        ld_path + ":" + os.environ['LD_LIBRARY_PATH']
    os.execve(os.path.realpath(__file__), sys.argv, os.environ)

sys.path.append(sys.path[0])
from gspylib.common.GaussLog import GaussLog
from gspylib.common.Common import DefaultValue, ClusterCommand
from gspylib.common.ParallelBaseOM import ParallelBaseOM
from gspylib.common.ErrorCode import ErrorCode
from gspylib.common.ParameterParsecheck import Parameter
from impl.install.OLAP.InstallImplOLAP import InstallImplOLAP
from domain_utils.cluster_file.cluster_config_file import ClusterConfigFile
from domain_utils.cluster_file.cluster_log import ClusterLog
from base_utils.os.env_util import EnvUtil
from base_utils.os.net_util import NetUtil
from base_utils.os.user_util import UserUtil
from domain_utils.domain_common.cluster_constants import ClusterConstants
# exit code
EXEC_SUCCESS = 0
ROLLBACK_FAILED = 3


class Install(ParallelBaseOM):
    """
    The class is used to do perform installation
    """

    def __init__(self):
        """
        function: initialize the parameters
        input : NA
        output: NA
        """
        ParallelBaseOM.__init__(self)
        self.time_out = None
        self.alarm_component = ""
        self.dbInitParam = []
        self.dataGucParam = []
        self.cm_server_guc_param = []
        self.action = "gs_install"
        self.initStep = "Init Install"
        self.enable_perf_config = False

    def usage(self):
        """
gs_install is a utility to deploy a cluster server.

Usage:
  gs_install -? | --help
  gs_install -V | --version
  gs_install -X XMLFILE [--gsinit-parameter="PARAMETER" [...]]
        [--dn-guc="PARAMETER" [...]] [--alarm-component=ALARMCOMPONENT]
        [--time-out=SECS] [-l LOGFILE]

General options:
  -X                                     Path of the XML configuration file.
  -l                                     Path of log file.
  -?, --help                             Show help information for this utility, and exit the command line mode.
  -V, --version                          Show version information.

  --gsinit-parameter="PARAMETER"         Parameters to initialize DN and CN.
                                         For more information, see \"gs_initdb --help\".
  --dn-guc="PARAMETER"                   Parameters to set the configuration of DN.
                                         For more information, see \"gs_guc --help\".
  --alarm-component=ALARMCOMPONENT       Path of the alarm component.
  --time-out=SECS                        Maximum waiting time when start cluster.
  --enable-perf-config                   Use gs_perfconfig to tune database setup and guc after installation.
        """
        print(self.usage.__doc__)

    def initGlobals(self):
        """
        function: Init logger
        input : NA
        output: NA
        """
        try:
            self.initLogger(self.action)
            self.logger.debug(
                "gs_install execution takes %s steps in total" % ClusterCommand.countTotalSteps(
                    self.action, "", self.readOperateStep()))
            self.logger.log("Parsing the configuration file.", "addStep")
            # parsing the configuration file, Parameter [refreshCN] does not refresh the CN number
            self.initClusterInfo(refreshCN=False)
            self.initComponent()
            # Initialize self.sshTool
            self.initSshTool(self.clusterInfo.getClusterNodeNames(),
                             DefaultValue.TIMEOUT_PSSH_INSTALL)
            if (len(self.clusterInfo.getClusterNodeNames()) == 1 and
                    self.clusterInfo.getClusterNodeNames()[0]
                    == NetUtil.GetHostIpOrName()):
                self.isSingle = True
                self.localMode = True
        except Exception as e:
            # failed to parse cluster config file
            raise Exception(str(e))
        # Successfully parsed the configuration file
        self.logger.debug("Successfully parsed the configuration file.",
                          "constant")

    def checkParaList(self, specialStr):
        """
        function:
        input:
        output:
        """
        VALUE_CHECK_LIST = ["|", ";", "&", "$", "<", ">", "`", "\\", "{", "}",
                            "(", ")", "[", "]", "~", "*", "?", "!", "\n"]
        VALUE_CHECK_GUC_PARA_LIST = ["client_encoding", "--encoding"]
        VALUE_CHECK_ENCODING_LIST = ["LATIN5", "ISO_8859_7", "KOI8U",
                                     "LATIN7", "EUC_TW", "WIN1251", "LATIN8",
                                     "KOI8R", "UTF8",
                                     "ISO_8859_5", "ISO_8859_8", "LATIN9",
                                     "LATIN6", "EUC_JP", "EUC_KR", "WIN1255",
                                     "EUC_CN",
                                     "LATIN3", "LATIN1", "ISO_8859_6", "GBK"]
        gs_checkStr = specialStr[0]
        if (gs_checkStr.strip() == ""):
            return
        for rac in VALUE_CHECK_LIST:
            flag = gs_checkStr.find(rac)
            if flag >= 0:
                raise Exception(ErrorCode.GAUSS_502["GAUSS_50219"]
                                % specialStr + " There are illegal "
                                               "characters in the parameter.")
        if (len(gs_checkStr.split("=")) != 2):
            return
        if (gs_checkStr.split("=")[1].strip().startswith("\'") is True and
            gs_checkStr.split("=")[1].strip().endswith("\'") is False) or \
                (gs_checkStr.split("=")[1].strip().startswith("\'") is False
                 and gs_checkStr.split("=")[1].strip().endswith(
                            "\'") is True):
            raise Exception(
                ErrorCode.GAUSS_502["GAUSS_50219"]
                % specialStr + " Lack of Paired Single "
                               "Quotation Marks.value %s" % gs_checkStr)
        if (gs_checkStr.split("=")[1].strip().startswith("\"") is True and
            gs_checkStr.split("=")[1].strip().endswith("\"") is False) \
                or (
                gs_checkStr.split("=")[1].strip().startswith("\"") is False
                and gs_checkStr.split("=")[1].strip().endswith("\"") is True):
            raise Exception(
                ErrorCode.GAUSS_502["GAUSS_50219"] % specialStr
                + " Lack of double quotation marks.value %s" % gs_checkStr)
        if gs_checkStr.split("=")[0].strip() in VALUE_CHECK_GUC_PARA_LIST and \
                (gs_checkStr.split("=")[1].strip().strip("\'").strip(
                    "\"").strip() not in VALUE_CHECK_ENCODING_LIST):
            raise Exception(
                ErrorCode.GAUSS_500["GAUSS_50011"] % (
                    gs_checkStr.split("=")[0],
                    gs_checkStr.split("=")[1].strip("\'").strip("\"").strip())
                + "Please cheak parameter '--dn-guc' or '--gsinit-parameter'.")

    def parseCommandLine(self):
        """
        function: Parse command line and save to global variable
        input : NA
        output: NA
        """
        # init the ParaObj
        ParaObj = Parameter()
        ParaDict = ParaObj.ParameterCommandLine("install")
        # parameter -h or -?
        if (ParaDict.__contains__("helpFlag")):
            self.usage()
            sys.exit(EXEC_SUCCESS)

        # parameter -X
        if (ParaDict.__contains__("confFile")):
            self.xmlFile = ParaDict.get("confFile")
        # parameter -l
        if (ParaDict.__contains__("logFile")):
            self.logFile = ParaDict.get("logFile")
        # parameter --gsinit-parameter
        if (ParaDict.__contains__("dbInitParams")):
            self.dbInitParam = ParaDict.get("dbInitParams")
            self.checkParaList(self.dbInitParam)
        # parameter --cmserver-guc
        if "cmServerGucParams" in ParaDict:
            self.cm_server_guc_param = ParaDict.get("cmServerGucParams")
            self.checkParaList(self.cm_server_guc_param)
        # parameter --dn-guc
        if (ParaDict.__contains__("dataGucParams")):
            self.dataGucParam = ParaDict.get("dataGucParams")
            self.checkParaList(self.dataGucParam)
        # parameter --alarm-component
        if (ParaDict.__contains__("alarm_component")):
            self.alarm_component = ParaDict.get("alarm_component")
        # parameter --time-out
        if (ParaDict.__contains__("time_out")):
            self.time_out = ParaDict.get("time_out")
        # parameter --dorado-cluster-mode
        if (ParaDict.__contains__("dorado-cluster-mode")):
            self.dorado_cluster_mode = ParaDict.get("dorado-cluster-mode")
        # parameter --enable-perf-config
        if (ParaDict.__contains__("enable_perf_config")):
            self.enable_perf_config = True

    def checkUser(self):
        """
        """
        # get user info
        self.user = UserUtil.getUserInfo()['name']
        # get the group info
        self.group = UserUtil.getUserInfo()['g_name']
        # check the user and group
        if (self.user == "" or self.group == ""):
            raise Exception(ErrorCode.GAUSS_503["GAUSS_50308"])
        if (self.user == "root" or self.group == "root"):
            raise Exception(ErrorCode.GAUSS_501["GAUSS_50105"])


    def checkDNPara(self):
        """
        """
        dnUnsupportedParameters = DefaultValue.findUnsupportedParameters(
            self.dataGucParam)
        if (len(dnUnsupportedParameters) != 0):
            GaussLog.printMessage("The following parameters set for database node will"
                                  " not take effect:\n%s"
                                  % str(dnUnsupportedParameters))
            for param in dnUnsupportedParameters:
                self.dataGucParam.remove(param)

    def checkAlarm(self):
        """
        """
        if (self.alarm_component == ""):
            self.alarm_component = DefaultValue.ALARM_COMPONENT_PATH
        if (not os.path.isabs(self.alarm_component)):
            raise Exception(
                ErrorCode.GAUSS_502["GAUSS_50213"] % "alarm component")


    def checkParameter(self):
        """
        function: Check parameter from command line 
        input : NA
        output: NA
        """
        # check required parameters
        self.checkUser()
        # check mpprc file path
        self.mpprcFile = EnvUtil.getMpprcFile()
        # check config file
        ClusterConfigFile.checkConfigFile(self.xmlFile)
        # check unsupported -D parameter
        self.checkDNPara()
        # check alarm component
        self.checkAlarm()
        # check logFile
        self.logFile = ClusterLog.checkLogFile(self.logFile, self.user, self.xmlFile,
                                               ClusterConstants.DEPLOY_LOG_FILE)


if __name__ == '__main__':
    """
    main function
    """
    # check if user is root
    if (os.getuid() == 0):
        GaussLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50105"])
    try:
        REPEAT = False
        # Objectize class
        install = Install()
        # Initialize self and Parse command line and save to global variable
        install.parseCommandLine()
        # check the parameters is not OK
        install.checkParameter()
        # Initialize globals parameters
        install.initGlobals()
        # set action flag file
        DefaultValue.setActionFlagFile("gs_install")
        
        impl = InstallImplOLAP(install)
        # Perform the whole install process
        impl.run()
    except Exception as e:
        GaussLog.exitWithError(str(e))
    finally:
        DefaultValue.setActionFlagFile("gs_install", False)
