############################################################################ # 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)