#!/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_preinstall is a utility to create an installation # environment for a cluster. ############################################################################# import os import pwd import sys import grp import subprocess import readline from gspylib.common.CheckPythonVersion import check_python_version, \ check_python_compiler_option, check_os_and_package_arch package_path = os.path.dirname(os.path.realpath(__file__)) lib_path = os.path.join(package_path, "..", "lib") clib_files = os.path.join(package_path, "gspylib/clib/*.so*") if os.listdir(lib_path): check_os_and_package_arch() check_python_version() check_python_compiler_option() from base_utils.os.file_util import FileUtil from gspylib.common.GaussLog import GaussLog if "--unused-third-party" in sys.argv: FileUtil.cleanDirectoryContent(lib_path) FileUtil.removeFile(clib_files) # use system pip dependecies import psutil import netifaces import cryptography import paramiko else: check_os_and_package_arch() check_python_version() check_python_compiler_option() from gspylib.common.copy_python_lib import copy_lib copy_lib() from gspylib.common.Common import DefaultValue from gspylib.common.ErrorCode import ErrorCode from gspylib.common.ParallelBaseOM import ParallelBaseOM from gspylib.common.ParameterParsecheck import Parameter from gspylib.common.DbClusterInfo import dbNodeInfo, \ dbClusterInfo, compareObject from impl.preinstall.OLAP.PreinstallImplOLAP import PreinstallImplOLAP from gspylib.threads.SshTool import SshTool from domain_utils.cluster_file.cluster_config_file import ClusterConfigFile from domain_utils.cluster_file.cluster_dir import ClusterDir from domain_utils.cluster_file.profile_file import ProfileFile from domain_utils.cluster_file.version_info import VersionInfo from domain_utils.cluster_os.cluster_user import ClusterUser from base_utils.os.net_util import NetUtil from base_utils.os.file_util import FileUtil from domain_utils.domain_common.cluster_constants import ClusterConstants from base_utils.os.user_util import UserUtil from base_utils.template.xml_template import GenerateTemplate from base_utils.os.hosts_util import HostsUtil ############################################################################# # Global variables ############################################################################# userNameFirtChar = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'] class Preinstall(ParallelBaseOM): def __init__(self): ParallelBaseOM.__init__(self) self.password = "" self.envParams = [] self.rootUser = "" self.rootPasswd = "" self.create_user_ssh_trust = True self.clusterToolPath = "" self.needFixOwnerPaths = [] self.preMode = False self.skipOSSet = False self.skipHostnameSet = False self.passwordsec = "" self.corePath = "" self.is_new_root_path = False self.ips = "" self.root_ssh_agent_flag = False self.root_delete_flag = False self.user_ssh_agent_flag = False self.enable_dss = "" self.dss_vg_info = "" self.dss_vgname = "" self.enable_perf_config = False self.skip_cgroup_set = False self.cluster_core_path = "" self.om_version_cfg = None # structure like {'pkg_prefix': '', 'pgversion': '', 'commit': '', 'mode': ''} self.og_version_cfg = None def usage(self): """ gs_preinstall is a utility to create an installation environment for a cluster. Usage: gs_preinstall -? | --help gs_preinstall -V | --version gs_preinstall -U USER -G GROUP -X XMLFILE [-L] [--skip-os-set] [--env-var="ENVVAR" [...]] [--sep-env-file=ENVFILE] [--skip-hostname-set] [-l LOGFILE] [--non-interactive] [--delete-root-trust] [--unused-third-party] General options: -U Cluster user. -G Group of the cluster user. -X Path of the XML configuration file. -L Only perform preinstallation on local nodes. --skip-os-set Whether to skip OS parameter setting. (The default value is set.) --env-var="ENVVAR" OS user environment variables. --sep-env-file=ENVFILE Path of the MPP environment file. --skip-hostname-set Whether to skip hostname setting. (The default value is set.) --skip-cgroup-set Whether to skip cgroup setting. --one-stop-install Interactive one stop installation (The default value is unset.) -l Path of log file. -?, --help Show help information for this utility, and exit the command line mode. -V, --version Show version information. --non-interactive Pre-execution of non-secure mode. If it is not specified, you can choose whether create the SSH trust for root user or cluster user. If it is specified, you must ensure the SSH trust for root user and cluster user have been created. --delete-root-trust Whether to delete root trust. (The default value is not deleted) --unused-third-party Whether to use om's third-party. (The default value is used) --enable-perf-config Use gs_perfconfig to tune os configuration after pre installation. """ print(self.usage.__doc__) # get parameter from command def parseCommandLine(self): """ function: Parse command line and save to global variable input: NA output: NA """ # init the ParaObj ParaObj = Parameter() ParaDict = ParaObj.ParameterCommandLine("preinstall") # parameter -h or -? if (ParaDict.__contains__("helpFlag")): self.usage() sys.exit(0) # check whether one-stop install one-stop-install if (ParaDict.__contains__("one_stop_install")): xml_template = GenerateTemplate() xml_template.run() self.xmlFile = xml_template.target_xml # check no root user paramters self.check_no_root_paramter(ParaDict) # Resolves command line arguments # parameter -U if (ParaDict.__contains__("user")): self.user = ParaDict.get("user") DefaultValue.checkPathVaild(self.user) # parameter -G if (ParaDict.__contains__("group")): self.group = ParaDict.get("group") # parameter -X if (ParaDict.__contains__("confFile") and not ParaDict.__contains__("one_stop_install")): self.xmlFile = ParaDict.get("confFile") # parameter -L if (ParaDict.__contains__("localMode")): self.localMode = ParaDict.get("localMode") # parameter -l if (ParaDict.__contains__("logFile")): self.logFile = ParaDict.get("logFile") # parameter --env-var if (ParaDict.__contains__("envparams")): self.envParams = ParaDict.get("envparams") # parameter --sep-env-file if (ParaDict.__contains__("mpprcFile")): self.mpprcFile = ParaDict.get("mpprcFile") DefaultValue.checkPathVaild(self.mpprcFile) # parameter --skip-hostname-set if (ParaDict.__contains__("skipHostnameSet")): self.skipHostnameSet = ParaDict.get("skipHostnameSet") # parameter --skip-cgroup-set if (ParaDict.__contains__("skip_cgroup_set")): self.skip_cgroup_set = ParaDict.get("skip_cgroup_set") # parameter --skip-os-set if (ParaDict.__contains__("skipOSSet")): self.skipOSSet = ParaDict.get("skipOSSet") # parameter --non-interactive if (ParaDict.__contains__("preMode")): self.preMode = ParaDict.get("preMode") # parameter --delete-root-trust if (ParaDict.__contains__("root_delete_flag")): self.root_delete_flag = ParaDict.get("root_delete_flag") # parameter --enable-perf-config if (ParaDict.__contains__("enable_perf_config")): self.enable_perf_config = True def check_no_root_paramter(self, para_dict): """ function: Check no root user paramter input: NA output: NA """ if not self.current_user_root: if (para_dict.__contains__("user") and (para_dict.get("user") != self.user)): GaussLog.exitWithError(ErrorCode.GAUSS_503["GAUSS_50324"]) if (para_dict.__contains__("group") and (para_dict.get("group") != self.group)): GaussLog.exitWithError(ErrorCode.GAUSS_503["GAUSS_50324"]) if not self.skipOSSet: GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50028"] % 'os') if not self.skip_cgroup_set: GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50028"] % 'cgroup') def checkUserParameter(self): """ """ if (self.user == ""): GaussLog.exitWithError( ErrorCode.GAUSS_500["GAUSS_50001"] % 'U' + ".") elif (":" in self.user): GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50004"] % 'U') # check if user exists cmd = "cat /etc/passwd|grep -v nologin|grep -v halt|" \ "grep -v shutdown|awk -F: '{ print $1 }'|" \ " grep '^%s$' 2>/dev/null" % self.user status = subprocess.getstatusoutput(cmd)[0] if status == 0: if pwd.getpwnam(self.user).pw_uid == 0: # user exists and uid is 0, exit. GaussLog.exitWithError(ErrorCode.GAUSS_503["GAUSS_50302"]) # check the local user and the localmode, # if user not exist exit with error if (self.localMode): try: DefaultValue.getUserId(self.user) except Exception as e: GaussLog.exitWithError(str(e)) def checkUserAndGroup(self): """ """ if (self.localMode): usergroup = grp.getgrgid(pwd.getpwnam(self.user).pw_gid).gr_name if (self.group != usergroup): GaussLog.exitWithError(ErrorCode.GAUSS_503["GAUSS_50305"] + "User:Group[%s:%s]" % (self.user, self.group)) def check_local_node_info(self): """ check local node info """ hostName = NetUtil.GetHostIpOrName() g_nodeInfo = self.clusterInfo.getDbNodeByName(hostName) if (g_nodeInfo is None): GaussLog.exitWithError(ErrorCode.GAUSS_516["GAUSS_51620"] % "local" + " It is not a host name %s." % hostName) def check_config_content(self, g_nodeInfo): UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("gaussdbAppPath", self.xmlFile)) UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("gaussdbToolPath", self.xmlFile)) UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("tmpMppdbPath", self.xmlFile)) UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("gaussdbLogPath", self.xmlFile)) UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("corePath", self.xmlFile)) # check cm UserUtil.check_path_owner(g_nodeInfo.cmDataDir) for cmaInst in g_nodeInfo.cmagents: UserUtil.check_path_owner(cmaInst.datadir) for cmsInst in g_nodeInfo.cmservers: UserUtil.check_path_owner(cmsInst.datadir) # check dn for dnInst in g_nodeInfo.datanodes: UserUtil.check_path_owner(dnInst.datadir) if (len(dnInst.ssdDir) != 0): UserUtil.check_path_owner(dnInst.ssdDir) # check dn xlog for dnInst in g_nodeInfo.datanodes: if dnInst.xlogdir != '': UserUtil.check_path_owner(dnInst.xlogdir) def checkEnvValueParameter(self): """ """ for param in self.envParams: # check environmental variables vaild illegal = ["|", ";", "&", "$", ">", "<", "`", "\\", "!", "\n"] if any(ill_char in param for ill_char in illegal): GaussLog.exitWithError( ErrorCode.GAUSS_500["GAUSS_50004"] % "-env-var" + " There are illegal characters in the parameter.") def checkLogFile(self): """ """ if (self.logFile == ""): self.logFile = self.getPreOMLogPath( ClusterConstants.PREINSTALL_LOG_FILE, self.xmlFile) if (not os.path.isabs(self.logFile)): GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50213"] % self.logFile) UserUtil.check_path_owner(self.logFile) def checkMpprcFile(self): """ """ if (self.mpprcFile == ""): return if (not os.path.isabs(self.mpprcFile)): GaussLog.exitWithError(ErrorCode.GAUSS_512["GAUSS_51206"] % self.mpprcFile) # check mpprc file path mpprcFilePath = os.path.normpath(self.mpprcFile) if os.path.islink(mpprcFilePath): GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_51251"] % mpprcFilePath) if (mpprcFilePath == "/home/%s" % self.user): GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50004"] % \ '-sep-env-file' + " The file [%s] can not" " be a reserved home " "directory." % self.mpprcFile) if (os.path.isdir(self.mpprcFile)): GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50004"] % \ '-sep-env-file' + " The file [%s] can not " "be a directory." % self.mpprcFile) ProfileFile.checkMpprcFileChange(self.mpprcFile, "", self.mpprcFile) (checkstatus, checkoutput) = ProfileFile.check_env_file(self.mpprcFile) if (not checkstatus): if (self.mpprcFile != ""): envfile = self.mpprcFile + " and /etc/profile" else: envfile = "/etc/profile and ~/.bashrc" GaussLog.exitWithError(ErrorCode.GAUSS_518["GAUSS_51808"] % \ checkoutput + "Please check %s." % envfile) def delete_host_ip(self): """ function: delete host_ip env input: NA output: NA """ if self.current_user_root: cmd = "sed -i '/^export[ ]*HOST_IP=/d' /etc/profile" (status, output) = subprocess.getstatusoutput(cmd) if status != 0: GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50205"] % ClusterConstants.ETC_PROFILE + "The cmd is %s" % cmd) if "HOST_IP" in os.environ.keys(): os.environ.pop("HOST_IP") def checkParameter(self): """ function: Check parameter from command line input: NA output: NA """ if self.current_user_root: # check -G parameter ClusterUser.checkGroupParameter(self.user, self.group) # remove HOST_IP info with /etc/profile and environ self.delete_host_ip() # check config file ClusterConfigFile.checkConfigFile(self.xmlFile) # check user info self.checkUserParameter() # check user group match self.checkUserAndGroup() # init cluster info self.initClusterInfo() # check local node info self.check_local_node_info() # check env-val self.checkEnvValueParameter() # check mpprc file self.checkMpprcFile() # check log file self.checkLogFile() # set LD_LIBRARY_PATH add local lib def setLibPath(self): package_path = os.path.dirname(os.path.realpath(__file__)) ld_path = package_path + "/gspylib/clib" rerun = True if 'LD_LIBRARY_PATH' not in os.environ: os.environ['LD_LIBRARY_PATH'] = ld_path elif not os.environ.get('LD_LIBRARY_PATH').startswith(ld_path): os.environ['LD_LIBRARY_PATH'] = \ ld_path + ":" + os.environ['LD_LIBRARY_PATH'] else: rerun = False if rerun: try: os.execve(os.path.realpath(__file__), sys.argv, os.environ) except Exception as e: GaussLog.exitWithError(str(e)) # decompress version.cfg from Server, and read it def readVersioncfg(self): def _parse_version_cfg(_cfg): _cmd = f'cat {_cfg}' _status, _output = subprocess.getstatusoutput(_cmd) if _status != 0: GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50217"] % "version.cfg" + "The cmd is %s. " % _cmd + "The output is %s." % _output) _lines = _output.splitlines() _res = { 'pkg_prefix': _lines[0].replace("OM", "Server"), 'pgversion': _lines[1], 'commit': _lines[2], 'mode': 'release' if len(_lines) < 4 else _lines[3] } return _res root = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..') cfg = os.path.join(root, 'version.cfg') # read version.cfg of om package self.om_version_cfg = _parse_version_cfg(cfg) # upack and read version.cfg of openGauss-server package # the existing om version.cfg will be overwritten cmd = 'cd {} && tar -xpf {}*.tar.bz2 ./version.cfg'.format(root, self.om_version_cfg['pkg_prefix']) status, output = subprocess.getstatusoutput(cmd) if status != 0: cmd = 'cd {} && tar -xpf `ls openGauss-Server*.tar.bz2 | tail -1` ./version.cfg'.format(root) status, output = subprocess.getstatusoutput(cmd) if status != 0: GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50217"] % "version.cfg" + "The cmd is %s. " % cmd + "The output is %s." % output) self.og_version_cfg = _parse_version_cfg(cfg) def unpackSomeToolsNeeded(self): pkg_prefix = self.om_version_cfg['pkg_prefix'] # target tools bin_files = ['./bin/encrypt'] dss_files = [] memcheck_files = [] if self.clusterInfo.enable_dss == 'on': dss_files = ['./bin/perctrl', './bin/dsscmd', './lib/libdssapi.so', './bin/dss_clear.sh'] if self.og_version_cfg['mode'] == 'memcheck': memcheck_files = ['./lib/libasan.so', './lib/libasan.so.6', './lib/libasan.so.6.0.0'] # target path root = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..') clib = os.path.join(root, "script/gspylib/clib") clib_dss = os.path.realpath(os.path.join(f'{clib}', f"dss_app_{self.og_version_cfg['commit']}")) memcheck_root_lib = '' if self.clusterInfo.enable_dss == 'on' and \ self.og_version_cfg['mode'] == 'memcheck' and \ not os.access('/usr/lib64/libasan.so.6', os.F_OK): memcheck_root_lib = '/usr/lib64' # unpack and move tools into target path cmd = 'cd {} && '.format(root) cmd += 'tar -xpf {}*.tar.bz2 {} && '.format(pkg_prefix, ' '.join(bin_files + dss_files + memcheck_files)) if dss_files: cmd += 'mkdir -p {0} -m u=rwx && '.format(clib_dss) cmd += 'mv {} {} && '.format(' '.join(dss_files), clib_dss) if memcheck_root_lib != '': cmd += 'cp ./lib/libasan.so.6 {} &&'.format(memcheck_root_lib) cmd += '\mv {} {} && '.format(' '.join(bin_files + memcheck_files), clib) cmd += 'cd {} && rm -rf bin'.format(root) status, output = subprocess.getstatusoutput(cmd) if status != 0: GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50217"] % "version.cfg" + "The cmd is %s. " % cmd + "The output is %s." % output) # init global variables def initGlobals(self): """ function: init global parameters input: NA output: NA """ # init the log file self.initLogger("gs_preinstall") current_path = os.path.dirname(os.path.realpath(__file__)) package_dir = os.path.normpath(os.path.join(current_path, "../")) self.write_host_file(package_dir) # get the clusterToolPath self.clusterToolPath = ClusterDir.getPreClusterToolPath(self.xmlFile) temp_nodes = ClusterConfigFile.getOneClusterConfigItem("nodeNames", self.xmlFile) if len(temp_nodes.split(',')) < 2: self.isSingle = True os.environ[ClusterConstants.TOOL_PATH_ENV] = self.clusterToolPath self.cluster_core_path = self.clusterInfo.readClustercorePath(self.xmlFile) self.logger.log("Parsing the configuration file.", "addStep") try: # parse the configuration file self.sshTool = SshTool(self.clusterInfo.getClusterNodeNames(), self.logFile, DefaultValue.TIMEOUT_PSSH_PREINSTALL) except Exception as e: self.logger.logExit(str(e)) # check the local hostname if NetUtil.GetHostIpOrName() not in \ self.clusterInfo.getClusterNodeNames(): self.logger.logExit(ErrorCode.GAUSS_516["GAUSS_51619"] % NetUtil.GetHostIpOrName()) self.logger.log("Successfully parsed the configuration file.", "constant") def write_host_file(self, package_dir): hosts_file = os.path.normpath(os.path.join(package_dir, "hosts")) if os.path.exists(hosts_file): FileUtil.removeFile(hosts_file) FileUtil.createFile(hosts_file) # hosts contents contents = {} hostname_list = self.clusterInfo.getClusterNodeNames() for hostname in hostname_list: node = self.clusterInfo.getDbNodeByName(hostname) ip = node.sshIps[0] contents[ip] = hostname HostsUtil.write_hosts_file(hosts_file, contents) FileUtil.changeMode(DefaultValue.FILE_MODE, hosts_file) # check expect for cm/create trust def check_expect(self): """ function: check expect input: NA output: NA """ temp_nodes = ClusterConfigFile.getOneClusterConfigItem("nodeNames", self.xmlFile) if len(temp_nodes.split(',')) > 1: cmd = "echo exit|expect" (status, _) = subprocess.getstatusoutput(cmd) if status != 0: GaussLog.exitWithError(ErrorCode.GAUSS_514["GAUSS_51405"] % "expect") def getPreOMLogPath(self, logName, xml): """ function: get the OM log path input: logName, xml output: fullLogPath """ try: fullLogPath = "" # get the log path configedLogPath = ClusterConfigFile.getOneClusterConfigItem("gaussdbLogPath", xml) DefaultValue.checkPathVaild(configedLogPath) # check gaussdbLogPath is not null if configedLogPath == "": fullLogPath = "%s/%s/om/%s" % ( ClusterConstants.GAUSSDB_DIR, self.user, logName) else: fullLogPath = "%s/%s/om/%s" % ( os.path.normpath(configedLogPath), self.user, logName) UserUtil.check_path_owner(fullLogPath) return fullLogPath except Exception as e: GaussLog.exitWithError(str(e)) def change_lib_path(self): """ if gs_preinstall current path is /root/gauss_om/username, so change its lib path :return: """ gsom_path = os.path.realpath( os.path.join(os.path.realpath(__file__), "../../../")) package_path = os.path.dirname(os.path.realpath(__file__)) lib_path = os.path.join(package_path, "..", "lib") sys.path.insert(0, lib_path) if gsom_path == DefaultValue.ROOT_SCRIPTS_PATH: self.is_new_root_path = True def checkIfNest(self): """ function: check syncNode """ clusterInfo = dbClusterInfo() clusterInfo.initFromXml(self.xmlFile) dbNodes = clusterInfo.dbNodes # get the dss_home path dss_home_path = clusterInfo.dss_home for dbinfo in dbNodes: if dbinfo is None: break datanodes = dbinfo.datanodes # check if subdir_path starts with parentdir_path for datainfo in datanodes: parentdir_path = os.path.normpath(datainfo.datadir) subdir_path = os.path.normpath(dss_home_path) if not parentdir_path.endswith(os.path.sep): parentdir_path += os.path.sep if not subdir_path.endswith(os.path.sep): subdir_path += os.path.sep if subdir_path.startswith(parentdir_path): GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50240"] % dss_home_path) def clear_hist_time_format(self): if self.current_user_root: cmd = "sed -i '/HISTTIMEFORMAT=/d' /etc/profile" (status, output) = subprocess.getstatusoutput(cmd) if status != 0: GaussLog.exitWithError("Clear HISTTIMEFORMAT from /etc/profile " "failed.\nError: %s\nThe cmd is: %s\n" % (output,cmd)) def check_current_user(self): user_info = UserUtil.getUserInfo() if user_info.get("uid") == 0: self.current_user_root = True else: self.current_user_root = False self.user = user_info.get("name") self.group = user_info.get("g_name") self.skipOSSet = True self.skip_cgroup_set = True def remove_mpp_env(): mpprc = os.environ.get('MPPDB_ENV_SEPARATE_PATH') if mpprc: os.environ.pop('MPPDB_ENV_SEPARATE_PATH') if __name__ == '__main__': """ main function """ try: # if not remove mpprc env, it will affect preinstall init remove_mpp_env() # Objectize class preinstall = Preinstall() # check if user is root preinstall.check_current_user() # remove HISTTIMEFORMAT with /etc/profile for root preinstall.clear_hist_time_format() # set LD_LIBRARY_PATH preinstall.setLibPath() # parse cmd lines preinstall.parseCommandLine() # check parameters preinstall.checkParameter() # check if the path between dss and datanode is nested preinstall.checkIfNest() # check expect preinstall.check_expect() # init global variables preinstall.initGlobals() # read version.cfg preinstall.readVersioncfg() # unpack some tools needed preinstall.unpackSomeToolsNeeded() preinstall.change_lib_path() impl = PreinstallImplOLAP(preinstall) # Perform the whole extand process impl.run() except Exception as e: GaussLog.exitWithError(str(e))