cm安装解耦

This commit is contained in:
xue_meng_en 2022-10-18 11:40:59 +08:00
parent fc46835e5d
commit 14bc8a3c91
8 changed files with 2418 additions and 1 deletions

View File

@ -150,7 +150,8 @@ function pkg() {
fi
cd ${OUT_PATH}
tar -czf "${bin_tar}" bin lib share
cp ${PROJECT_ROOT_PATH}/tool . -R
tar -czf "${bin_tar}" bin lib share tool
if [ -d symbols ]; then
tar -czf "${sym_tar}" symbols
fi

398
tool/cm_tool/CMLog.py Normal file
View File

@ -0,0 +1,398 @@
# -*- coding:utf-8 -*-
#############################################################################
# Portions Copyright (c) 2022 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 : CMLog.py is utility to handle the log
#############################################################################
import os
import sys
import datetime
import subprocess
import _thread as thread
import re
import logging.handlers as _handlers
import time
sys.path.append(sys.path[0] + "/../../")
from ErrorCode import ErrorCode
# import typing for comment.
try:
from typing import Dict
from typing import List
except ImportError:
Dict = dict
List = list
# max log file size
# 16M
MAXLOGFILESIZE = 16 * 1024 * 1024
LOG_DEBUG = 1
LOG_INFO = 2
LOG_WARNING = 2.1
LOG_ERROR = 3
LOG_FATAL = 4
class CMLog:
"""
Class to handle log file
"""
def __init__(self, logPath, module, prefix, suffix = ".log", expectLevel=LOG_DEBUG, traceId=None):
"""
function: Constructor
input : NA
output: NA
"""
self.logFile = ""
self.expectLevel = expectLevel
self.moduleName = module
self.fp = None
self.size = 0
self.suffix = suffix
self.prefix = prefix
self.logPath = logPath
self.pid = os.getpid()
self.step = 0
self.lock = thread.allocate_lock()
self.tmpFile = None
self.ignoreErr = False
self.traceId = traceId
try:
if not os.path.isdir(logPath):
print(ErrorCode.GAUSS_502["GAUSS_50211"] % logPath)
sys.exit(1)
# check log path
if not os.path.exists(logPath):
try:
os.makedirs(logPath, 0o700)
except Exception as e:
raise Exception(ErrorCode.GAUSS_502["GAUSS_50208"] %
logPath + " Error:\n%s" % str(e))
# create new log file
self.__openLogFile()
except Exception as ex:
print(str(ex))
sys.exit(1)
def __checkLink(self):
"""
function: check log file is link
input : NA
output: list of
"""
if os.path.islink(self.logFile):
raise Exception(ErrorCode.GAUSS_502["GAUSS_50206"] % self.logFile)
def __checkLogFileExist(self):
"""
check whether log file exists, if exist, get log file name
log file name format:
prefix-YYYY-mm-DD_HHMMSSsuffix = cm_install-YYYY-mm-DD_HHMMSS.log
"""
logFileList = "%s/logFileList_%s.dat" % (self.logPath, self.pid)
cmd = "ls %s | grep '^%s-.*%s$' > %s" % (
self.logPath, self.prefix, self.suffix, logFileList)
(status, output) = subprocess.getstatusoutput(cmd)
if status != 0:
if os.path.exists(logFileList):
os.remove(logFileList)
return False
with open(logFileList, "r") as fp:
filenameList = []
while True:
# get real file name
filename = (fp.readline()).strip()
if not filename:
break
existedResList = filename.split(".")
if len(existedResList) > 2:
continue
(existedPrefix, existedSuffix) = \
os.path.splitext(filename)
if existedSuffix != self.suffix:
continue
if len(filename) != len(self.prefix) + \
len(self.suffix) + 18:
continue
timeStamp = existedPrefix[-17:]
# check log file name
if self.__isValidDate(timeStamp):
filenameList.append(filename)
# cleanup logFileList
if os.path.exists(logFileList):
os.remove(logFileList)
if len(filenameList) == 0:
return False
# get logFile
fileName = max(filenameList)
self.logFile = os.path.join(self.logPath, fileName)
self.__checkLink()
return True
def __openLogFile(self):
"""
function: open log file
input : NA
output: NA
"""
try:
if self.__checkLogFileExist():
self.fp = open(self.logFile, "a")
return
# get current time
currentTime = time.strftime("%Y-%m-%d_%H%M%S")
# init log file
self.logFile = os.path.join(self.logPath, self.prefix + "-" + currentTime + self.suffix)
# Re-create the log file to add a retry 3 times mechanism,
# in order to call concurrently between multiple processes
retryTimes = 3
count = 0
while (True):
(status, output) = self.__createLogFile()
if status == 0:
break
count = count + 1
time.sleep(1)
if (count > retryTimes):
raise Exception(output)
# open log file
self.__checkLink()
self.fp = open(self.logFile, "a")
except Exception as e:
raise Exception(ErrorCode.GAUSS_502["GAUSS_50206"]
% self.logFile + " Error:\n%s" % str(e))
def __createLogFile(self):
"""
function: create log file
input : NA
output: (status, output)
"""
try:
if (not os.path.exists(self.logFile)):
os.mknod(self.logFile)
return (0, "")
except Exception as e:
return (1, str(e))
def __isValidDate(self, datastr):
"""
function: Judge if date valid
input : datastr
output: bool
"""
try:
time.strptime(datastr, "%Y-%m-%d_%H%M%S")
return True
except Exception as ex:
return False
def closeLog(self):
"""
function: Function to close log file
input : NA
output: NA
"""
try:
if self.fp:
self.fp.flush()
self.fp.close()
self.fp = None
except Exception as ex:
if self.fp:
self.fp.close()
raise Exception(str(ex))
# print the flow message to console window and log file
# AddInfo: constant represent step constant, addStep represent step
# plus, None represent no step
def log(self, msg, stepFlag=""):
"""
function:print the flow message to console window and log file
input: msg,stepFlag
control: when stepFlag="", the OM background log does not display
step information.
when stepFlag="addStep", the OM background log step will
add 1.
when stepFlag="constant", the OM background log step
defaults to the current step.
output: NA
"""
if (LOG_INFO >= self.expectLevel):
print(msg)
self.__writeLog("LOG", msg, stepFlag)
# print the flow message to log file only
def debug(self, msg, stepFlag=""):
"""
function:print the flow message to log file only
input: msg,stepFlag
control: when stepFlag="", the OM background log does not display
step information.
when stepFlag="addStep", the OM background log step will
add 1.
when stepFlag="constant", the OM background log step
defaults to the current step.
output: NA
"""
if (LOG_DEBUG >= self.expectLevel):
self.__writeLog("DEBUG", msg, stepFlag)
def warn(self, msg, stepFlag=""):
"""
function:print the flow message to log file only
input: msg,stepFlag
control: when stepFlag="", the OM background log does not display
step information.
when stepFlag="addStep", the OM background log step will
add 1.
when stepFlag="constant", the OM background log step
defaults to the current step.
output: NA
"""
if (LOG_WARNING >= self.expectLevel):
print(msg)
self.__writeLog("WARNING", msg, stepFlag)
# print the error message to console window and log file
def error(self, msg):
"""
function: print the error message to console window and log file
input : msg
output: NA
"""
if (LOG_ERROR >= self.expectLevel):
print(msg)
self.__writeLog("ERROR", msg)
# print the error message to console window and log file,then exit
def logExit(self, msg):
"""
function: print the error message to console window and log file,
then exit
input : msg
output: NA
"""
if (LOG_FATAL >= self.expectLevel):
print(msg)
try:
self.__writeLog("ERROR", msg)
except Exception as ex:
print(str(ex))
self.closeLog()
sys.exit(1)
def Step(self, stepFlag):
"""
function: return Step number info
input: add
output: step number
"""
if (stepFlag == "constant"):
return self.step
else:
self.step = self.step + 1
return self.step
def __getLogFileLine(self):
f = sys._getframe().f_back.f_back.f_back
return "%s(%s:%s)" % (os.path.basename(f.f_code.co_filename), f.f_code.co_name,
str(f.f_lineno))
def __writeLog(self, level, msg, stepFlag=""):
"""
function: Write log to file
input: level, msg, stepFlag
output: NA
"""
if self.fp is None:
return
try:
self.lock.acquire()
# if the log file does not exits, create it
if (not os.path.exists(self.logFile)):
self.__openLogFile()
else:
logPer = oct(os.stat(self.logFile).st_mode)[-3:]
self.__checkLink()
if not logPer == "600":
os.chmod(self.logFile, 0o600)
# check if need switch to an new log file
self.size = os.path.getsize(self.logFile)
if self.size >= MAXLOGFILESIZE and os.getuid() != 0:
self.closeLog()
self.__openLogFile()
replaceReg = re.compile(r'-W[ ]*[^ ]*[ ]*')
msg = replaceReg.sub('-W *** ', str(msg))
if msg.find("gs_redis") >= 0:
replaceReg = re.compile(r'-A[ ]*[^ ]*[ ]*')
msg = replaceReg.sub('-A *** ', str(msg))
strTime = datetime.datetime.now()
fileLine = self.__getLogFileLine()
if stepFlag == "":
if self.traceId:
print("[%s][%s][%d][%s][%s]:%s"
% (self.traceId, strTime, self.pid, self.moduleName,
level, msg), file=self.fp)
else:
print("[%s][%d][%s][%s]:%s" % (
strTime, self.pid, self.moduleName, level, msg),
file=self.fp)
else:
stepnum = self.Step(stepFlag)
print("[%s][%d][%s][%s][%s][Step%d]:%s" % (
strTime, self.pid, fileLine, self.moduleName, level, stepnum, msg),
file=self.fp)
self.fp.flush()
self.lock.release()
except Exception as ex:
self.lock.release()
if self.ignoreErr:
return
raise Exception(ErrorCode.GAUSS_502["GAUSS_50205"]
% (("log file %s") % self.logFile) +
" Error:\n%s" % str(ex))
@staticmethod
def exitWithError(msg, status=1):
"""
function: Exit with error message
input: msg, status=1
output: NA
"""
sys.stderr.write("%s\n" % msg)
sys.exit(status)
@staticmethod
def printMessage(msg):
"""
function: Print the String message
input: msg
output: NA
"""
sys.stdout.write("%s\n" % msg)

42
tool/cm_tool/Common.py Normal file
View File

@ -0,0 +1,42 @@
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2022 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 : Common.py includes some public function
#############################################################################
import subprocess
from CMLog import CMLog
from ErrorCode import ErrorCode
def getEnvParam(envFile, param):
cmd = "source {envFile}; echo ${param}".format(envFile=envFile, param=param)
status, output = subprocess.getstatusoutput(cmd)
if status != 0:
errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s\n" % (
cmd, status, output)
CMLog.exitWithError(ErrorCode.GAUSS_518["GAUSS_51802"] % param)
return output
def getLocalhostName():
import socket
return socket.gethostname()
def executeCmdOnHost(host, cmd, isLocal = False):
if not isLocal:
cmd = 'ssh -o ConnectTimeout=5 %s \"%s\"' % (host, cmd)
status, output = subprocess.getstatusoutput(cmd)
return status, output

1173
tool/cm_tool/ErrorCode.py Normal file

File diff suppressed because it is too large Load Diff

307
tool/cm_tool/InstallImpl.py Normal file
View File

@ -0,0 +1,307 @@
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2022 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 : InstallImpl.py
#############################################################################
import os
import re
import subprocess
import xml.etree.cElementTree as ETree
from ErrorCode import ErrorCode
from Common import executeCmdOnHost
from CMLog import CMLog
class InstallImpl:
def __init__(self, install):
self.cmpkg = install.cmpkg
self.context = install
self.envFile = install.envFile
self.xmlFile = install.xmlFile
self.cmDirs = install.cmDirs
self.hostNames = install.hostNames
self.gaussHome = install.gaussHome
self.gaussLog = install.gaussLog
self.toolPath = install.toolPath
self.tmpPath = install.tmpPath
self.localhostName = install.localhostName
self.logger = install.logger
def executeCmdOnHost(self, host, cmd, isLocal = False):
if host == self.localhostName:
isLocal = True
return executeCmdOnHost(host, cmd, isLocal)
def prepareCMPath(self):
"""
create path: cmdircmdir/cm_servercmdir/cm_agent
"""
self.logger.log("Preparing CM path.")
for (cmdir, host) in zip(self.cmDirs, self.hostNames):
cmd = "mkdir -p {cmdir}/cm_server {cmdir}/cm_agent".format(cmdir=cmdir)
status, output = self.executeCmdOnHost(host, cmd)
if status != 0:
self.logger.debug("Command: " + cmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit("Failed to create CM path." + errorDetail)
def decompressCMPkg(self):
self.logger.log("Decompressing CM pacakage.")
if self.cmpkg == "":
return
# decompress cm pkg on localhost
decompressCmd = "tar -zxf %s -C %s" % (self.cmpkg, self.gaussHome)
status, output = subprocess.getstatusoutput(decompressCmd)
if status != 0:
self.logger.debug("Command: " + decompressCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit("Failed to decompress cm pacakage to on localhost." + errorDetail)
# If the version of CM pacakage is inconsistent with that of gaussdb,
# then exit. So no need to send CM pacakage to other nodes.
self.checkCMPkgVersion()
# decompress cmpkg on other hosts
cmpkgName = os.path.basename(self.cmpkg)
for host in self.hostNames:
if host == self.localhostName:
continue
# copy cm pacakage to other hosts
scpCmd = "scp %s %s:%s" % (self.cmpkg, host, self.toolPath)
status, output = subprocess.getstatusoutput(scpCmd)
if status != 0:
self.logger.debug("Command: " + scpCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit(("Failed to send cm pacakage to %s." % host) + errorDetail)
pkgPath = os.path.join(self.toolPath, cmpkgName)
decompressCmd = "tar -zxf %s -C %s" % (pkgPath, self.gaussHome)
status, output = self.executeCmdOnHost(host, decompressCmd)
if status != 0:
self.logger.debug("Command: " + decompressCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit(("Failed to decompress cm pacakage to on host %s." % host) + errorDetail)
def checkCMPkgVersion(self):
getCMVersionCmd = "source %s; cm_ctl -V" % self.envFile
status, output = subprocess.getstatusoutput(getCMVersionCmd)
if status != 0:
self.logger.logExit("Failed to get CM pacakage version.")
cmVersionList = re.findall(r'openGauss CM (\d.*\d) build', output)
if len(cmVersionList) == 0:
self.logger.logExit("Failed to get CM pacakage version.")
cmVersion = cmVersionList[0]
getGaussdbVersionCmd = "source %s; gaussdb -V" % self.envFile
status, output = subprocess.getstatusoutput(getGaussdbVersionCmd)
if status != 0:
self.logger.logExit("Failed to get gaussdb version.")
gaussdbVersionList = re.findall(r'openGauss (\d.*\d) build', output)
if len(gaussdbVersionList) == 0:
self.logger.logExit("Failed to get gaussdb version.")
gaussdbVersion = gaussdbVersionList[0]
if gaussdbVersion != cmVersion:
self.logger.logExit("The version of CM pacakage(%s) is inconsistent "
"with that of gaussdb(%s)." % (cmVersion, gaussdbVersion))
def createManualStartFile(self):
self.logger.log("Creating cluster_manual_start file.")
cmd = """
if [ ! -f {gaussHome}/bin/cluster_manual_start ]; then
touch {gaussHome}/bin/cluster_manual_start
fi
""".format(gaussHome=self.gaussHome)
for host in self.hostNames:
status, output = self.executeCmdOnHost(host, cmd)
if status != 0:
self.logger.debug("Command: " + cmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit("Failed to create cluster_manual_start file." + errorDetail)
def initCMServer(self):
self.logger.log("Initializing cm_server.")
for (cmdir, host) in zip(self.cmDirs, self.hostNames):
cmd = """
cp {gaussHome}/share/config/cm_server.conf.sample {cmdir}/cm_server/cm_server.conf
sed 's#log_dir = .*#log_dir = {gaussLog}/cm/cm_server#' {cmdir}/cm_server/cm_server.conf -i
""".format(gaussHome=self.gaussHome, gaussLog=self.gaussLog, cmdir=cmdir)
status, output = self.executeCmdOnHost(host, cmd)
if status != 0:
self.logger.debug("Command: " + cmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit("Failed to initialize cm_server." + errorDetail)
def initCMAgent(self):
self.logger.log("Initializing cm_agent.")
for (cmdir, host) in zip(self.cmDirs, self.hostNames):
cmd = """
cp {gaussHome}/share/config/cm_agent.conf.sample {cmdir}/cm_agent/cm_agent.conf &&
sed 's#log_dir = .*#log_dir = {gaussLog}/cm/cm_agent#' {cmdir}/cm_agent/cm_agent.conf -i &&
sed 's#unix_socket_directory = .*#unix_socket_directory = {gaussHome}#' {cmdir}/cm_agent/cm_agent.conf -i
""".format(gaussHome=self.gaussHome, gaussLog=self.gaussLog, cmdir=cmdir)
status, output = self.executeCmdOnHost(host, cmd)
if status != 0:
self.logger.debug("Command: " + cmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit("Failed to initialize cm_agent." + errorDetail)
def setMonitorCrontab(self):
"""
set om_monitor crontab
"""
self.logger.log("Setting om_monitor crontab.")
# save old crontab content to cronContentTmpFile
cronContentTmpFile = os.path.join(self.tmpPath, "cronContentTmpFile_" + str(os.getpid()))
listCronCmd = "crontab -l > %s" % cronContentTmpFile
status, output = self.executeCmdOnHost(self.localhostName, listCronCmd)
if status != 0:
self.logger.debug("Command: " + listCronCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit(ErrorCode.GAUSS_508["GAUSS_50804"] + errorDetail)
# if old crontab content contains om_monitor, clear it
clearMonitorCmd = "sed '/.*om_monitor.*/d' %s -i" % cronContentTmpFile
status, output = subprocess.getstatusoutput(clearMonitorCmd)
if status != 0:
os.remove(cronContentTmpFile)
self.logger.debug("Command: " + clearMonitorCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit("Failed to clear old om_monitor crontab." + errorDetail)
# generate om_monitor crontab command and append it to cronContentTmpFile
startMonitorCmd = "source /etc/profile;(if [ -f ~/.profile ];" \
"then source ~/.profile;fi);source ~/.bashrc;"
if self.envFile != "~/.bashrc":
startMonitorCmd += "source %s; " % (self.envFile)
monitorLogPath = os.path.join(self.gaussLog, "cm")
if not os.path.exists(monitorLogPath):
os.makedirs(monitorLogPath)
startMonitorCmd += "nohup om_monitor -L %s/om_monitor >>/dev/null 2>&1 &" % monitorLogPath
monitorCron = "*/1 * * * * " + startMonitorCmd + os.linesep
with open(cronContentTmpFile, 'a+', encoding='utf-8') as fp:
fp.writelines(monitorCron)
fp.flush()
# set crontab on other hosts
setCronCmd = "crontab %s" % cronContentTmpFile
cleanTmpFileCmd = "rm %s -f" % cronContentTmpFile
for host in self.hostNames:
if host == self.localhostName:
continue
# copy cronContentTmpFile to other host
scpCmd = "scp %s %s:%s" % (cronContentTmpFile, host, self.tmpPath)
status, output = subprocess.getstatusoutput(scpCmd)
if status != 0:
self.logger.debug("Command: " + scpCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit(("Failed to copy cronContentTmpFile to %s." % host) + errorDetail)
# set om_monitor crontab
status, output = self.executeCmdOnHost(host, setCronCmd)
# cleanup cronContentTmpFile
self.executeCmdOnHost(host, cleanTmpFileCmd)
if status != 0:
self.logger.debug("Command: " + setCronCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit(ErrorCode.GAUSS_508["GAUSS_50801"] + errorDetail)
# start om_monitor
status, output = self.executeCmdOnHost(host, startMonitorCmd)
if status != 0:
self.logger.debug("Command: " + startMonitorCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit((ErrorCode.GAUSS_516["GAUSS_51607"] % "om_monitor") + errorDetail)
# set crontab on localhost
status, output = subprocess.getstatusoutput(setCronCmd)
if status != 0:
self.logger.debug("Command: " + setCronCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit(ErrorCode.GAUSS_508["GAUSS_50801"] + errorDetail)
os.remove(cronContentTmpFile)
status, output = subprocess.getstatusoutput(startMonitorCmd)
if status != 0:
self.logger.debug("Command: " + startMonitorCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit((ErrorCode.GAUSS_516["GAUSS_51607"] % "om_monitor") + errorDetail)
def startCluster(self):
self.logger.log("Starting cluster.")
startCmd = "source %s; cm_ctl start" % self.envFile
status, output = subprocess.getstatusoutput(startCmd)
if status != 0:
self.logger.debug("Command: " + startCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit("Failed to start cluster." + errorDetail)
queryCmd = "source %s; cm_ctl query -Cv" % self.envFile
status, output = subprocess.getstatusoutput(queryCmd)
if status != 0:
self.logger.debug("Command: " + queryCmd)
errorDetail = "\nStatus: %s\nOutput: %s" % (status, output)
self.logger.logExit("Failed to query cluster status." + errorDetail)
self.logger.log(output)
self.logger.log("Install CM tool success.")
@staticmethod
def refreshStaticFile(envFile, xmlFile):
"""
refresh static and dynamic file using xml file with cm
"""
# refresh static file
cmd = """
source {envFile};
gs_om -t generateconf -X {xmlFile} --distribute
""".format(envFile=envFile, xmlFile=xmlFile)
status, output = subprocess.getstatusoutput(cmd)
errorDetail = ""
if status != 0:
errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (cmd, status, output)
return status, errorDetail
@staticmethod
def refreshDynamicFile(envFile):
# refresh dynamic file
getStatusCmd = "source %s; gs_om -t status --detail | grep 'Primary Normal' > /dev/null" % envFile
status, output = subprocess.getstatusoutput(getStatusCmd)
if status != 0:
CMLog.printMessage("Normal primary doesn't exist in the cluster, no need to refresh dynamic file.")
return 0, ""
refreshDynamicFileCmd = "source %s; gs_om -t refreshconf" % envFile
status, output = subprocess.getstatusoutput(refreshDynamicFileCmd)
errorDetail = ""
if status != 0:
errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (refreshDynamicFileCmd, status, output)
return status, errorDetail
def refreshStaticAndDynamicFile(self):
self.logger.log("Refreshing static and dynamic file using xml file with cm.")
status, output = InstallImpl.refreshStaticFile(self.envFile, self.xmlFile)
if status != 0:
self.logger.logExit("Failed to refresh static file." + output)
status, output = InstallImpl.refreshDynamicFile(self.envFile)
if status != 0:
self.logger.logExit("Failed to refresh dynamic file." + output)
def run(self):
self.logger.log("Start to install cm tool.")
self.prepareCMPath()
self.decompressCMPkg()
self.createManualStartFile()
self.initCMServer()
self.initCMAgent()
self.refreshStaticAndDynamicFile()
self.setMonitorCrontab()
self.startCluster()

0
tool/cm_tool/__init__.py Normal file
View File

247
tool/cm_tool/cm_install Normal file
View File

@ -0,0 +1,247 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2022 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 : cm_install is a utility to deploy CM tool to openGauss database cluster.
#############################################################################
import getopt
import os
import sys
import re
import subprocess
import xml.etree.cElementTree as ETree
from CMLog import CMLog
from Common import *
from ErrorCode import ErrorCode
from InstallImpl import InstallImpl
sys.path.append(sys.path[0] + "/../../")
class Install:
"""
The class is used to do perform installation
"""
def __init__(self):
self.envFile = ""
self.xmlFile = ""
self.gaussHome = ""
self.gaussLog = ""
self.toolPath = ""
self.tmpPath = ""
self.cmDirs = []
self.hostNames = []
self.localhostName = ""
self.cmpkg = ""
def getLocalhostName(self):
import socket
self.localhostName = socket.gethostname()
def getEnvParams(self):
self.gaussHome = getEnvParam(self.envFile, "GAUSSHOME")
self.gaussLog = getEnvParam(self.envFile, "GAUSSLOG")
self.toolPath = getEnvParam(self.envFile, "GPHOME")
self.tmpPath = getEnvParam(self.envFile, "PGHOST")
def checkXMLFile(self):
"""
function: check XML file
1.check -X parameter
2.check XML file exists
3.check XML file an absolute path
4.permission
input : NA
output: NA
"""
if self.xmlFile.startswith('~/'):
homePath = os.path.expanduser('~')
self.xmlFile = homePath + self.xmlFile[1:]
if not os.path.isfile(self.xmlFile):
CMLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50210"] % "xmlFile")
if not os.path.exists(self.xmlFile):
CMLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50201"] % "xmlFile")
if not os.path.isabs(self.xmlFile):
CMLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50213"] % "xmlFile")
if not os.access(self.xmlFile, os.R_OK):
CMLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50100"] % (self.xmlFile, "current user"))
def checkExeUser(self):
if os.getuid() == 0:
CMLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50105"])
def usage(self):
"""
cm_install is a utility to deploy CM tool to openGauss database cluster.
Usage:
cm_install -? | --help
cm_install -X XMLFILE [-e envFile] --cmpkg=cmpkgPath
General options:
-X Path of the XML configuration file.
-e Path of env file.
Default value "~/.bashrc".
--cmpkg Path of CM pacakage.
-?, --help Show help information for this
utility, and exit the command line mode.
"""
print(self.usage.__doc__)
def parseCommandLine(self):
if len(sys.argv) == 1:
self.usage()
sys.exit(1)
try:
opts, args = getopt.getopt(sys.argv[1:], "?X:e:", ["help", "cmpkg="])
except getopt.GetoptError as e:
CMLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50000"] % str(e))
for opt, value in opts:
if opt in ("-?", "--help"):
self.usage()
sys.exit(0)
elif opt in ("-X"):
self.xmlFile = value
elif opt in ("-e"):
self.envFile = value
elif opt in ("--cmpkg"):
self.cmpkg = value
def checkParam(self):
if self.xmlFile == "":
CMLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50001"] % 'X or xml' + ".")
if self.cmpkg == "":
CMLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50001"] % 'p or cmpkg' + ".")
if self.envFile == "":
self.envFile = "~/.bashrc"
def checkOm(self):
"""
check whether there is om tool
"""
cmd = "source %s; gs_om --version" % self.envFile
status, output = subprocess.getstatusoutput(cmd)
if status != 0:
errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s\n" % (
cmd, status, output)
CMLog.exitWithError("OM tool is required." + errorDetail)
def checkXMLFileSecurity(self):
"""
function : check XML contain DTDs
input : String
output : NA
"""
# Check xml for security requirements
# if it have "<!DOCTYPE" or it have "<!ENTITY",
# exit and print "File have security risks."
try:
with open(self.xmlFile, "r", encoding='utf-8') as fb:
lines = fb.readlines()
for line in lines:
if re.findall("<!DOCTYPE", line) or re.findall("<!ENTITY", line):
raise Exception("File have security risks.")
except Exception as e:
raise Exception(str(e))
def initParserXMLFile(self):
"""
function : Init parser xml file
input : String
output : Object
"""
try:
# check xml for security requirements
self.checkXMLFileSecurity()
dom_tree = ETree.parse(self.xmlFile)
rootNode = dom_tree.getroot()
except Exception as e:
raise Exception(ErrorCode.GAUSS_512["GAUSS_51236"] + " Error: \n%s." % str(e))
return rootNode
def getInfoListOfAllNodes(self):
"""
get hostname and cmDir list of all nodes
"""
self.localhostName = getLocalhostName()
rootNode = self.initParserXMLFile()
elementName = 'DEVICELIST'
if not rootNode.findall('DEVICELIST'):
raise Exception(ErrorCode.GAUSS_512["GAUSS_51200"] % elementName)
deviceArray = rootNode.findall('DEVICELIST')[0]
deviceNodes = deviceArray.findall('DEVICE')
for dev in deviceNodes:
paramList = dev.findall('PARAM')
for param in paramList:
if param.attrib['name'] == 'name':
self.hostNames.append(param.attrib['value'])
if param.attrib['name'] == 'cmDir':
self.cmDirs.append(param.attrib['value'])
def _checkTrust(self, host):
"""
check trust between current host and the given host
"""
checkTrustCmd = "source %s; pssh -s -H %s 'pwd'" % (self.envFile, host)
status, output = subprocess.getstatusoutput(checkTrustCmd)
return status
def checkHostTrust(self):
for host in self.hostNames:
if host == self.localhostName:
continue
if self._checkTrust(host) != 0:
CMLog.exitWithError(ErrorCode.GAUSS_511["GAUSS_51100"] % host)
def initLogger(self):
logPath = os.path.join(self.gaussLog, "cm", "cm_tool")
if not os.path.exists(logPath):
os.makedirs(logPath)
self.logger = CMLog(logPath, "cm_install", "cm_install")
def checkCM(self):
"""
Check whether there is CM in current cluster.
"""
checkCMExistCmd = "source %s; gs_om -t status --detail | " \
"grep 'CMServer State' > /dev/null" % self.envFile
status, output = subprocess.getstatusoutput(checkCMExistCmd)
if status == 0:
CMLog.exitWithError("CM exists in current cluster.")
def run(self):
self.checkExeUser()
self.parseCommandLine()
self.checkParam()
self.checkOm()
self.checkCM()
self.checkXMLFile()
self.getEnvParams()
self.initLogger()
self.getLocalhostName()
self.getInfoListOfAllNodes()
self.checkHostTrust()
installImpl = InstallImpl(self)
installImpl.run()
if __name__ == "__main__":
install = Install()
install.run()

249
tool/cm_tool/cm_uninstall Normal file
View File

@ -0,0 +1,249 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2022 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 : cm_install is a utility to uninstall CM tool to openGauss database cluster.
#############################################################################
import os
import sys
import getopt
import subprocess
from CMLog import CMLog
from ErrorCode import ErrorCode
from InstallImpl import InstallImpl
from Common import executeCmdOnHost, getEnvParam, getLocalhostName
class UnIntall:
def __init__(self) -> None:
self.envFile = ""
self.xmlFile = ""
self.bDeleteData = False
self.bDeleteBinary = False
self.hostnames = []
self.cmDataPaths = []
self.localhostName = getLocalhostName()
def usage(self):
"""
cm_uninstall is a utility to deploy CM tool to openGauss database cluster.
Usage:
cm_uninstall -? | --help
cm_uninstall -X XMLFILE [-e envFile] [--deleteData] [--deleteBinary]
General options:
-X Path of the XML configuration file.
-e Path of env file.
Default value "~/.bashrc".
--deleteData Specify to true if you want to remove cmdatapath and GAUSSLOG/cm.
--deleteBinary Specify to true if you want to remove CM binary file.
-?, --help Show help information for this
utility, and exit the command line mode.
"""
print(self.usage.__doc__)
def parseCommandLine(self):
if len(sys.argv) == 1:
self.usage()
sys.exit(1)
try:
opts, args = getopt.getopt(sys.argv[1:], "?X:e:",
["help", "deleteData", "deleteBinary"])
except getopt.GetoptError as e:
CMLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50000"] % str(e))
for opt, value in opts:
if opt in ("-?", "--help"):
self.usage()
sys.exit(0)
elif opt in ("-X"):
self.xmlFile = value
elif opt in ("-e"):
self.envFile = value
elif opt in ("--deleteData"):
self.bDeleteData = True
elif opt in ("--deleteBinary"):
self.bDeleteBinary = True
def checkParam(self):
if self.xmlFile == "":
CMLog.exitWithError(
ErrorCode.GAUSS_500["GAUSS_50001"] % 'X' + ".")
if self.envFile == "":
self.envFile = "~/.bashrc"
def getHostnames(self):
"""
Get hostnames of all nodes from static file.
"""
self.logger.debug("Getting hostnames")
cmd = "source %s; cm_ctl view | grep 'nodeName'" % self.envFile
status, output = subprocess.getstatusoutput(cmd)
if status != 0:
self.logger.logExit((ErrorCode.GAUSS_512["GAUSS_51203"] % "hostnames") + output)
hostnames = output.split('\n')
for i in range(0, len(hostnames)):
hostname = hostnames[i].split(':')[1].strip()
self.hostnames.append(hostname)
self.logger.debug("hostnames=" + ','.join(self.hostnames))
def getCMDataPaths(self):
"""
Get cmDataPaths of all nodes from static file.
"""
if not self.bDeleteData:
return
self.logger.debug("Getting CM datapaths.")
cmd = "source %s; cm_ctl view | grep -E 'cmDataPath'" % self.envFile
status, output = subprocess.getstatusoutput(cmd)
if status != 0:
self.logger.logExit((ErrorCode.GAUSS_512["GAUSS_51203"] % "cmDataPaths") + output)
cmDataPaths = output.split('\n')
for p in cmDataPaths:
cmDataPath = p.split(':')[1].strip()
self.cmDataPaths.append(cmDataPath)
if self.cmDataPaths == []:
self.logger.logExit("")
self.logger.debug("cmDataPaths=" + ','.join(self.cmDataPaths))
def cancleMonitorCrontab(self):
"""
Cancle monitor crontab of all nodes.
"""
self.logger.log("Cancling monitor crontab.")
cronContentTmpFile = os.path.join("/tmp", "cronContentTmpFile_" + str(os.getpid()))
cmd = """
crontab -l > {cronContentTmpFile};
sed '/.*om_monitor.*/d' {cronContentTmpFile} -i;
crontab {cronContentTmpFile}
""".format(cronContentTmpFile=cronContentTmpFile)
for host in self.hostnames:
status, output = executeCmdOnHost(host, cmd)
if status != 0:
errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (cmd, status, output)
self.logger.logExit(("Failed to cancle monitor crontab on host %s." % host) + errorDetail)
def stopCMProcess(self):
"""
Stop CM process of all nodes, includeing om_monitor, cm_agent, cm_server, gaussdb fence
"""
self.logger.log("Stopping CM process.")
# When you execute the awk command through Python, sometimes the awk command is invalid,
# just like your command does not use the awk command. Therefore, we have to avoid using awk.
getPidsCmd = "ps -xo pid,command | grep -E 'om_monitor|cm_agent|cm_server|fenced UDF' | grep -v grep"
for host in self.hostnames:
isLocal = False
if host == self.localhostName:
isLocal = True
# get CM process pids
status, output = executeCmdOnHost(host, getPidsCmd, isLocal)
self.logger.debug("Command: " + getPidsCmd)
self.logger.debug("Status: " + str(status))
self.logger.debug("Output: " + output)
if output == "":
continue
processList = output.strip().split('\n')
pidList = []
for process in processList:
pid = process.split()[0]
if pid.isdigit():
pidList.append(pid)
if pidList == []:
continue
# kill CM process
pidsStr = ' '.join(pidList)
killCMProcessCmd = "kill -9 " + pidsStr
status, output = executeCmdOnHost(host, killCMProcessCmd, isLocal)
if status != 0:
errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (killCMProcessCmd, status, output)
self.logger.logExit((ErrorCode.GAUSS_516["GAUSS_51606"] % "CM") + errorDetail)
def refreshStaticAndDynamicFile(self):
self.logger.log("Refreshing static and dynamic file using xml file without cm.")
status, output = InstallImpl.refreshStaticFile(self.envFile, self.xmlFile)
if status != 0:
self.logger.logExit("Failed to refresh static file." + output)
status, output = InstallImpl.refreshDynamicFile(self.envFile)
if status != 0:
self.logger.logExit("Failed to refresh dynamic file." + output)
def deleteData(self):
"""
remove cmdatapath
"""
if not self.bDeleteData:
return
self.logger.log("Deleting CM data path.")
for host, path in zip(self.hostnames, self.cmDataPaths):
isLocal = False
if host == self.localhostName:
isLocal = True
cmd = "source %s; rm -rf %s" % (self.envFile, path)
status, output = executeCmdOnHost(host, cmd, isLocal)
if status != 0:
errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (cmd, status, output)
self.logger.exitWithError(("Failed to delete CM data path on host %s." % host) + errorDetail)
def deleteBinary(self):
"""
delete CM binaries, include om_monitor, cm_agent, cm_server, cm_ctl
"""
if not self.bDeleteBinary:
return
self.logger.log("Deleting CM binaries.")
for host in self.hostnames:
isLocal = False
if host == self.localhostName:
isLocal = True
cmd = "source %s; cd $GAUSSHOME/bin; rm -f om_monitor cm_agent cm_server cm_ctl; cd -" % self.envFile
status, output = executeCmdOnHost(host, cmd, isLocal)
if status != 0:
errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (cmd, status, output)
self.logger.exitWithError(("Failed to delete CM binaries on host %s." % host) + errorDetail)
def initLogger(self):
gaussLog = getEnvParam(self.envFile, "GAUSSLOG")
logPath = os.path.join(gaussLog, "cm", "cm_tool")
if not os.path.exists(logPath):
os.makedirs(logPath)
self.logger = CMLog(logPath, module="cm_uninstall", prefix="cm_uninstall")
def checkExeUser(self):
if os.getuid() == 0:
CMLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50105"])
def run(self):
self.checkExeUser()
self.parseCommandLine()
self.checkParam()
self.initLogger()
self.logger.log("Start to uninstall CM.")
self.getHostnames()
self.getCMDataPaths()
self.cancleMonitorCrontab()
self.stopCMProcess()
self.refreshStaticAndDynamicFile()
self.deleteData()
self.deleteBinary()
self.logger.log("Uninstall CM tool success.")
if __name__ == "__main__":
unInstall = UnIntall()
unInstall.run()