You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
12 KiB
Python
387 lines
12 KiB
Python
1 year ago
|
############################################################################
|
||
|
# Copyright (C) by gempa GmbH, GFZ Potsdam #
|
||
|
# #
|
||
|
# You can redistribute and/or modify this program under the #
|
||
|
# terms of the SeisComP Public License. #
|
||
|
# #
|
||
|
# This program is distributed in the hope that it will be useful, #
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||
|
# SeisComP Public License for more details. #
|
||
|
############################################################################
|
||
|
|
||
|
from __future__ import print_function
|
||
|
import os
|
||
|
import sys
|
||
|
import time
|
||
|
import string
|
||
|
import subprocess
|
||
|
import seiscomp.config
|
||
|
|
||
|
|
||
|
class Template(string.Template):
|
||
|
idpattern = r'[_a-z][_a-z0-9.]*'
|
||
|
|
||
|
|
||
|
class Environment(seiscomp.config.Config):
|
||
|
def __init__(self, rootPath):
|
||
|
seiscomp.config.Config.__init__(self)
|
||
|
self.SEISCOMP_ROOT = rootPath
|
||
|
try:
|
||
|
self.home_dir = os.environ["HOME"]
|
||
|
except:
|
||
|
self.home_dir = "."
|
||
|
|
||
|
try:
|
||
|
self.local_config_dir = os.environ["SEISCOMP_LOCAL_CONFIG"]
|
||
|
except:
|
||
|
self.local_config_dir = os.path.join(self.home_dir, ".seiscomp")
|
||
|
|
||
|
self.root = rootPath
|
||
|
self.bin_dir = os.path.join(self.root, "bin")
|
||
|
self.data_dir = os.path.join(self.root, "share")
|
||
|
self.etc_dir = os.path.join(self.root, "etc")
|
||
|
self.etc_defaults_dir = os.path.join(self.root, "etc", "defaults")
|
||
|
self.descriptions_dir = os.path.join(self.root, "etc", "descriptions")
|
||
|
self.key_dir = os.path.join(self.root, "etc", "key")
|
||
|
self.var_dir = os.path.join(self.root, "var")
|
||
|
self.log_dir = os.path.join(self.local_config_dir, "log")
|
||
|
self.cwd = None
|
||
|
self.last_template_file = None
|
||
|
|
||
|
self._csv = False
|
||
|
self._readConfig()
|
||
|
|
||
|
os.environ["SEISCOMP_ROOT"] = self.SEISCOMP_ROOT
|
||
|
|
||
|
# Add LD_LIBRARY_PATH and PATH to OS environment
|
||
|
LD_LIBRARY_PATH = os.path.join(self.SEISCOMP_ROOT, "lib")
|
||
|
BIN_PATH = os.path.join(self.SEISCOMP_ROOT, "bin")
|
||
|
SBIN_PATH = os.path.join(self.SEISCOMP_ROOT, "sbin")
|
||
|
PATH = BIN_PATH + ":" + SBIN_PATH
|
||
|
PYTHONPATH = os.path.join(self.SEISCOMP_ROOT, "lib", "python")
|
||
|
try:
|
||
|
LD_LIBRARY_PATH = os.environ["LD_LIBRARY_PATH"] + \
|
||
|
":" + LD_LIBRARY_PATH
|
||
|
except:
|
||
|
pass
|
||
|
os.environ["LD_LIBRARY_PATH"] = LD_LIBRARY_PATH
|
||
|
try:
|
||
|
PATH = PATH + ":" + os.environ["PATH"]
|
||
|
except:
|
||
|
pass
|
||
|
os.environ["PATH"] = PATH
|
||
|
try:
|
||
|
PYTHONPATH = os.environ["PYTHONPATH"] + ":" + PYTHONPATH
|
||
|
except:
|
||
|
pass
|
||
|
os.environ["PYTHONPATH"] = PYTHONPATH
|
||
|
|
||
|
# Create required directories
|
||
|
try:
|
||
|
os.makedirs(os.path.join(self.root, "var", "log"))
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
try:
|
||
|
os.makedirs(os.path.join(self.root, "var", "run"))
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
def _readConfig(self):
|
||
|
self.syslog = False
|
||
|
|
||
|
# Read configuration file
|
||
|
kernelCfg = os.path.join(self.root, "etc", "kernel.cfg")
|
||
|
if self.readConfig(kernelCfg) == False:
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
self.syslog = self.getBool("syslog")
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
# Changes into the SEISCOMP_ROOT directory
|
||
|
def chroot(self):
|
||
|
if self.root:
|
||
|
# Remember current directory
|
||
|
self.cwd = os.getcwd()
|
||
|
os.chdir(self.SEISCOMP_ROOT)
|
||
|
self.root = ""
|
||
|
|
||
|
# Changes back to the current workdir
|
||
|
def chback(self):
|
||
|
if self.cwd:
|
||
|
os.chdir(self.cwd)
|
||
|
self.cwd = None
|
||
|
self.root = self.SEISCOMP_ROOT
|
||
|
|
||
|
def resolvePath(self, path):
|
||
|
return path.replace("@LOGDIR@", self.log_dir)\
|
||
|
.replace("@CONFIGDIR@", self.local_config_dir)\
|
||
|
.replace("@DEFAULTCONFIGDIR@", self.etc_defaults_dir)\
|
||
|
.replace("@SYSTEMCONFIGDIR@", self.etc_dir)\
|
||
|
.replace("@ROOTDIR@", self.root)\
|
||
|
.replace("@DATADIR@", self.data_dir)\
|
||
|
.replace("@KEYDIR@", self.key_dir)\
|
||
|
.replace("@HOMEDIR@", self.home_dir)
|
||
|
|
||
|
def setCSVOutput(self, csv):
|
||
|
self._csv = csv
|
||
|
|
||
|
def enableModule(self, name):
|
||
|
runFile = os.path.join(self.root, "etc", "init", name + ".auto")
|
||
|
if os.path.exists(runFile):
|
||
|
print("%s is already enabled" % name)
|
||
|
return 0
|
||
|
try:
|
||
|
open(runFile, 'w').close()
|
||
|
print("enabled %s" % name)
|
||
|
return 0
|
||
|
except Exception as exc:
|
||
|
sys.stderr.write(str(exc) + "\n")
|
||
|
sys.stderr.flush()
|
||
|
return 0
|
||
|
|
||
|
def disableModule(self, name):
|
||
|
runFile = os.path.join(self.root, "etc", "init", name + ".auto")
|
||
|
if not os.path.exists(runFile):
|
||
|
print("%s is not enabled" % name)
|
||
|
return 0
|
||
|
try:
|
||
|
os.remove(runFile)
|
||
|
print("disabled %s" % name)
|
||
|
except Exception as exc:
|
||
|
sys.stderr.write(str(exc) + "\n")
|
||
|
sys.stderr.flush()
|
||
|
return 0
|
||
|
|
||
|
def isModuleEnabled(self, name):
|
||
|
runFile = os.path.join(self.root, "etc", "init", name + ".auto")
|
||
|
return os.path.exists(runFile) == True
|
||
|
|
||
|
# Return the module name from a path
|
||
|
def moduleName(self, path):
|
||
|
return os.path.splitext(os.path.basename(path))[0]
|
||
|
|
||
|
# Returns a module's lockfile
|
||
|
def lockFile(self, module):
|
||
|
return os.path.join(self.root, "var", "run", module + ".pid")
|
||
|
|
||
|
# Returns a module's runfile
|
||
|
def runFile(self, module):
|
||
|
return os.path.join(self.root, "var", "run", module + ".run")
|
||
|
|
||
|
# Returns a module's logfile
|
||
|
def logFile(self, module):
|
||
|
return os.path.join(self.root, "var", "log", module + ".log")
|
||
|
|
||
|
# Returns the binary file path of a given module name
|
||
|
def binaryFile(self, module):
|
||
|
# return os.path.join(self.root, "bin/" + module)
|
||
|
return module
|
||
|
|
||
|
def start(self, module, binary, params, nohup=False):
|
||
|
cmd = binary + " " + params + " >" + self.logFile(module) + " 2>&1"
|
||
|
if nohup:
|
||
|
cmd = "nohup " + cmd + " &"
|
||
|
return os.system(cmd)
|
||
|
|
||
|
def stop(self, module, timeout):
|
||
|
return self.killWait(module, timeout)
|
||
|
|
||
|
def tryLock(self, module, timeout = None):
|
||
|
if timeout is None:
|
||
|
return subprocess.call("trylock " + self.lockFile(module), shell=True) == 0
|
||
|
else:
|
||
|
try:
|
||
|
timeoutSeconds = int(timeout)
|
||
|
except:
|
||
|
print("Invalid timeout parameter, expected positive integer")
|
||
|
raise
|
||
|
return subprocess.call("waitlock %d \"%s\"" % (timeoutSeconds, self.lockFile(module)), shell=True) == 0
|
||
|
|
||
|
def killWait(self, module, timeout):
|
||
|
lockfile = self.lockFile(module)
|
||
|
|
||
|
# Open pid file
|
||
|
f = open(lockfile, "r")
|
||
|
|
||
|
# Try to read the pid
|
||
|
try:
|
||
|
pid = int(f.readline())
|
||
|
except:
|
||
|
f.close()
|
||
|
raise
|
||
|
|
||
|
# Kill process with pid
|
||
|
subprocess.call("kill %d" % pid, shell=True)
|
||
|
if subprocess.call("waitlock %d \"%s\"" % (timeout, lockfile), shell=True) != 0:
|
||
|
print("timeout exceeded")
|
||
|
subprocess.call("kill -9 %d" % pid, shell=True)
|
||
|
|
||
|
# Remove pid file
|
||
|
try:
|
||
|
os.remove(lockfile)
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
return True
|
||
|
|
||
|
def processTemplate(self, templateFile, paths, params, printError=False):
|
||
|
self.last_template_file = None
|
||
|
|
||
|
for tp in paths:
|
||
|
if os.path.exists(os.path.join(tp, templateFile)):
|
||
|
break
|
||
|
|
||
|
else:
|
||
|
if printError:
|
||
|
print("Error: template %s not found" % templateFile)
|
||
|
return ""
|
||
|
|
||
|
filename = os.path.join(tp, templateFile)
|
||
|
self.last_template_file = filename
|
||
|
|
||
|
try:
|
||
|
t = Template(open(filename).read())
|
||
|
except:
|
||
|
if printError:
|
||
|
print("Error: template %s not readable" % filename)
|
||
|
return ""
|
||
|
|
||
|
params['date'] = time.ctime()
|
||
|
params['template'] = filename
|
||
|
|
||
|
while True:
|
||
|
try:
|
||
|
return t.substitute(params)
|
||
|
|
||
|
except KeyError as e:
|
||
|
print("warning: $%s is not defined in %s" % (e.args[0], filename))
|
||
|
params[e.args[0]] = ""
|
||
|
|
||
|
except ValueError as e:
|
||
|
raise ValueError("%s: %s" % (filename, str(e)))
|
||
|
|
||
|
def logStatus(self, name, isRunning, shouldRun, isEnabled):
|
||
|
if self._csv == False:
|
||
|
sys.stdout.write("%-20s is " % name)
|
||
|
if not isRunning:
|
||
|
sys.stdout.write("not ")
|
||
|
sys.stdout.write("running")
|
||
|
if not isRunning and shouldRun:
|
||
|
sys.stdout.write(" [WARNING]")
|
||
|
sys.stdout.write("\n")
|
||
|
else:
|
||
|
sys.stdout.write("%s;%d;%d;%d\n" % (
|
||
|
name, int(isRunning), int(shouldRun), int(isEnabled)))
|
||
|
sys.stdout.flush()
|
||
|
|
||
|
def log(self, line):
|
||
|
sys.stdout.write(line + "\n")
|
||
|
sys.stdout.flush()
|
||
|
|
||
|
|
||
|
# The module interface which implementes the basic default operations.
|
||
|
# Each script can define its own handlers to customize the behaviour.
|
||
|
# Available handlers:
|
||
|
# start()
|
||
|
# stop()
|
||
|
# check()
|
||
|
# status(shouldRun)
|
||
|
# setup(params = dict{name, values as []})
|
||
|
# updateConfig()
|
||
|
class Module:
|
||
|
def __init__(self, env, name):
|
||
|
self.env = env
|
||
|
self.name = name
|
||
|
# The start order
|
||
|
self.order = 100
|
||
|
# Defines if this is a kernel module or not.
|
||
|
# Kernel modules are always started
|
||
|
self.isKernelModule = False
|
||
|
# Defines if this is a config only module
|
||
|
self.isConfigModule = False
|
||
|
# Set default timeout when stopping a module to 10 seconds before killing it
|
||
|
self.killTimeout = 10
|
||
|
# Set default timeout when reloading a module to 10 seconds
|
||
|
self.reloadTimeout = 10
|
||
|
|
||
|
def _get_start_params(self):
|
||
|
# Run as daemon
|
||
|
params = "-D"
|
||
|
|
||
|
# Enable syslog if configured
|
||
|
if self.env.syslog == True:
|
||
|
params = params + "s"
|
||
|
|
||
|
params = params + " -l " + self.env.lockFile(self.name)
|
||
|
return params
|
||
|
|
||
|
def _run(self):
|
||
|
return self.env.start(self.name, self.env.binaryFile(self.name), self._get_start_params())
|
||
|
|
||
|
def isRunning(self):
|
||
|
return self.env.tryLock(self.name) == False
|
||
|
|
||
|
def start(self):
|
||
|
if self.isRunning():
|
||
|
self.env.log("%s is already running" % self.name)
|
||
|
return 1
|
||
|
|
||
|
self.env.log("starting %s" % self.name)
|
||
|
return self._run()
|
||
|
|
||
|
def stop(self):
|
||
|
if not self.isRunning():
|
||
|
self.env.log("%s is not running" % self.name)
|
||
|
return 1
|
||
|
|
||
|
self.env.log("shutting down %s" % self.name)
|
||
|
# Default timeout to 10 seconds
|
||
|
return self.env.stop(self.name, self.killTimeout)
|
||
|
|
||
|
def reload(self):
|
||
|
self.env.log("reload not supported by %s" % self.name)
|
||
|
return 1
|
||
|
|
||
|
# Check is the same as start. If a module should be checked
|
||
|
# is decided by the control script which check the existence
|
||
|
# of a corresponding run file.
|
||
|
def check(self):
|
||
|
return self.start()
|
||
|
|
||
|
def status(self, shouldRun):
|
||
|
self.env.logStatus(self.name, self.isRunning(), shouldRun, self.env.isModuleEnabled(
|
||
|
self.name) or isinstance(self, CoreModule))
|
||
|
|
||
|
def requiresKernelModules(self):
|
||
|
# The default handler triggers a start of kernel modules before updating
|
||
|
# its configuration
|
||
|
return True
|
||
|
|
||
|
def updateConfigProxy(self):
|
||
|
# This function must return either a string containing the module name
|
||
|
# of the proxy module that should be configured as well or None.
|
||
|
return None
|
||
|
|
||
|
def updateConfig(self):
|
||
|
# This function must return a number indicating the error code where
|
||
|
# 0 means no error. The default handler doesn't do anything.
|
||
|
return 0
|
||
|
|
||
|
def printCrontab(self):
|
||
|
# The default handler doesn't do anything
|
||
|
return 0
|
||
|
|
||
|
def supportsAliases(self):
|
||
|
# The default handler does not support aliases
|
||
|
return False
|
||
|
|
||
|
|
||
|
# Define a kernel core module which is started always
|
||
|
class CoreModule(Module):
|
||
|
def __init__(self, env, name):
|
||
|
Module.__init__(self, env, name)
|