class Loader(MySupport): def __init__(self, start=False, stop=False, hardstop=False, loglocation="/var/log/", log=None, localinit=False, ConfigFilePath=None): self.Start = start self.Stop = stop self.HardStop = hardstop if ConfigFilePath == None: self.ConfigFilePath = "/etc/" else: self.ConfigFilePath = ConfigFilePath self.ConfigFileName = "genloader.conf" # log errors in this module to a file if localinit == True: self.configfile = self.ConfigFileName else: self.configfile = self.ConfigFilePath + self.ConfigFileName self.ModulePath = os.path.dirname(os.path.realpath(__file__)) + "/" self.ConfPath = os.path.dirname(os.path.realpath(__file__)) + "/conf/" # log errors in this module to a file if log == None: self.log = SetupLogger("genloader", loglocation + "genloader.log") else: self.log = log self.console = SetupLogger("genloader_console", log_file="", stream=True) try: if self.Start: if not self.CheckSystem(): self.LogInfo("Error check system readiness. Exiting") sys.exit(2) self.CachedConfig = {} # check to see if genloader.conf is present, if not copy it from genmon directory if not os.path.isfile(self.configfile): self.LogInfo("Warning: unable to find config file: " + self.configfile + " Copying file to /etc/ directory.") if os.path.isfile(self.ConfPath + self.ConfigFileName): copyfile(self.ConfPath + self.ConfigFileName, self.configfile) else: self.LogInfo("Unable to find config file.") sys.exit(2) self.config = MyConfig(filename=self.configfile, section="genmon", log=self.log) if not self.GetConfig(): self.LogInfo("Error reading config file. Exiting") sys.exit(2) if not self.ValidateConfig(): self.LogInfo("Error validating config. Exiting") sys.exit(2) self.LoadOrder = self.GetLoadOrder() if self.Stop: self.StopModules() time.sleep(2) if self.Start: self.StartModules() except Exception as e1: self.LogErrorLine("Error in init: " + str(e1)) #--------------------------------------------------------------------------- def CheckSystem(self): # this function checks the system to see if the required libraries are # installed. If they are not then an attempt is made to install them. ModuleList = [ # [import name , install name] ['flask', 'flask'], ['configparser', 'configparser'], ['serial', 'pyserial'], ['crcmod', 'crcmod'], ['pyowm', 'pyowm'], ['pytz', 'pytz'] ] try: ErrorOccured = False for Module in ModuleList: if not self.LibraryIsInstalled(Module[0]): self.LogInfo("Warning: required library " + Module[1] + " not installed. Attempting to install....") if not self.InstallLibrary(Module[1]): self.LogInfo("Error: unable to install library " + Module[1]) ErrorOccured = True return not ErrorOccured except Exception as e1: self.LogInfo("Error in CheckSystem: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def LibraryIsInstalled(self, libraryname): try: import importlib my_module = importlib.import_module(libraryname) return True except Exception as e1: return False #--------------------------------------------------------------------------- def InstallLibrary(self, libraryname): try: process = Popen(['pip', 'install', libraryname], stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in InstallLibrary using pip : " + libraryname + " : " + str(_error)) rc = process.returncode return True except Exception as e1: self.LogInfo("Error installing module: " + libraryname + ": " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def ValidateConfig(self): ErrorOccured = False if not len(self.CachedConfig): self.LogInfo("Error: Empty configruation found.") return False for Module, Settiings in self.CachedConfig.items(): try: if self.CachedConfig[Module]["enable"]: if not os.path.isfile(self.ModulePath + self.CachedConfig[Module]["module"]): self.LogInfo("Enable to find file " + self.ModulePath + self.CachedConfig[Module]["module"]) ErrorOccured = True # validate config file and if it is not there then copy it. if not self.CachedConfig[Module]["conffile"] == None and len( self.CachedConfig[Module]["conffile"]): ConfFileList = self.CachedConfig[Module]["conffile"].split( ",") for ConfigFile in ConfFileList: ConfigFile = ConfigFile.strip() if not os.path.isfile(self.ConfigFilePath + ConfigFile): if os.path.isfile(self.ConfPath + ConfigFile): self.LogInfo("Copying " + ConfigFile + " to " + self.ConfigFilePath) copyfile(self.ConfPath + ConfigFile, self.ConfigFilePath + ConfigFile) else: self.LogInfo("Enable to find config file " + self.ConfPath + ConfigFile) ErrorOccured = True except Exception as e1: self.LogInfo("Error validating config for " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def AddEntry(self, section=None, module=None, conffile="", args="", priority='2'): try: if section == None or module == None: return self.config.WriteSection(section) self.config.WriteValue('module', module, section=section) self.config.WriteValue('enable', 'False', section=section) self.config.WriteValue('hardstop', 'False', section=section) self.config.WriteValue('conffile', conffile, section=section) self.config.WriteValue('args', args, section=section) self.config.WriteValue('priority', priority, section=section) except Exception as e1: self.LogInfo("Error in AddEntry: " + str(e1), LogLine=True) return #--------------------------------------------------------------------------- def UpdateIfNeeded(self): try: self.config.SetSection("gengpioin") if not self.config.HasOption('conffile'): self.config.WriteValue('conffile', "gengpioin.conf", section="gengpioin") self.LogError("Updated entry gengpioin.conf") else: defValue = self.config.ReadValue('conffile', default="") if not len(defValue): self.config.WriteValue('conffile', "gengpioin.conf", section="gengpioin") self.LogError("Updated entry gengpioin.conf") except Exception as e1: self.LogInfo("Error in UpdateIfNeeded: " + str(e1), LogLine=True) #--------------------------------------------------------------------------- def GetConfig(self): try: Sections = self.config.GetSections() ValidSections = [ 'genmon', 'genserv', 'gengpio', 'gengpioin', 'genlog', 'gensms', 'gensms_modem', 'genpushover', 'gensyslog', 'genmqtt', 'genslack', 'genexercise' ] for entry in ValidSections: if not entry in Sections: if entry == 'genslack': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genslack.py', conffile='genslack.conf') if entry == 'genexercise': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genexercise.py', conffile='genexercise.conf') else: self.LogError("Warning: Missing entry: " + entry) self.UpdateIfNeeded() Sections = self.config.GetSections() for SectionName in Sections: TempDict = {} self.config.SetSection(SectionName) if self.config.HasOption('module'): TempDict['module'] = self.config.ReadValue('module') else: TempDict['module'] = None if self.config.HasOption('enable'): TempDict['enable'] = self.config.ReadValue( 'enable', return_type=bool) else: TempDict['enable'] = False if self.config.HasOption('hardstop'): TempDict['hardstop'] = self.config.ReadValue( 'hardstop', return_type=bool) else: TempDict['hardstop'] = False if self.config.HasOption('conffile'): TempDict['conffile'] = self.config.ReadValue('conffile') else: TempDict['conffile'] = None if self.config.HasOption('args'): TempDict['args'] = self.config.ReadValue('args') else: TempDict['args'] = None if self.config.HasOption('priority'): TempDict['priority'] = self.config.ReadValue( 'priority', return_type=int, default=None) else: TempDict['priority'] = None if self.config.HasOption('postloaddelay'): TempDict['postloaddelay'] = self.config.ReadValue( 'postloaddelay', return_type=int, default=0) else: TempDict['postloaddelay'] = 0 self.CachedConfig[SectionName] = TempDict return True except Exception as e1: self.LogInfo("Error parsing config file: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def ConvertToInt(self, value, default=None): try: return int(str(value)) except: return default #--------------------------------------------------------------------------- def GetLoadOrder(self): LoadOrder = [] LoadDict = {} try: for Module, Settiings in self.CachedConfig.items(): # get the load order of all modules, even if they are disabled # since we need to stop all modules (even disabled ones) if the # conf file changed try: if self.CachedConfig[Module]["priority"] == None: LoadDict[Module] = 99 elif self.CachedConfig[Module]["priority"] >= 0: LoadDict[Module] = self.CachedConfig[Module][ "priority"] else: LoadDict[Module] = 99 except Exception as e1: self.LogInfo("Error reading load order (retrying): " + str(e1), LogLine=True) #lambda kv: (-kv[1], kv[0]) for key, value in sorted(LoadDict.items(), key=lambda kv: (-kv[1], kv[0])): #for key, value in sorted(LoadDict.iteritems(), key=lambda (k,v): (v,k)): LoadOrder.append(key) except Exception as e1: self.LogInfo("Error reading load order: " + str(e1), LogLine=True) return LoadOrder #--------------------------------------------------------------------------- def StopModules(self): self.LogConsole("Stopping....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to stop.") return False ErrorOccured = False for Module in self.LoadOrder: try: if not self.UnloadModule( self.CachedConfig[Module]["module"], HardStop=self.CachedConfig[Module]["hardstop"]): self.LogInfo("Error stopping " + Module) ErrorOccured = True except Exception as e1: self.LogInfo("Error stopping module " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def StartModules(self): self.LogConsole("Starting....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to start.") return False ErrorOccured = False for Module in reversed(self.LoadOrder): try: if self.CachedConfig[Module]["enable"]: if not self.LoadModule( self.ModulePath + self.CachedConfig[Module]["module"], args=self.CachedConfig[Module]["args"]): self.LogInfo("Error starting " + Module) ErrorOccured = True if not self.CachedConfig[Module][ "postloaddelay"] == None and self.CachedConfig[ Module]["postloaddelay"] > 0: time.sleep(self.CachedConfig[Module]["postloaddelay"]) except Exception as e1: self.LogInfo("Error starting module " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def LoadModuleAlt(self, modulename, args=None): try: self.LogConsole("Starting " + modulename) # to load as a background process we just use os.system since Popen # is problematic in doing this CommandString = sys.executable + " " + modulename if args != None and len(args): CommandString += " " + args CommandString += " &" os.system(CommandString) return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def LoadModule(self, modulename, args=None): try: self.LogConsole("Starting " + modulename) try: from subprocess import DEVNULL # py3k except ImportError: import os DEVNULL = open(os.devnull, 'wb') if not len(args): args = None if "genserv.py" in modulename: OutputStream = DEVNULL else: OutputStream = subprocess.PIPE if args == None: # close_fds=True pid = subprocess.Popen([sys.executable, modulename], stdout=OutputStream, stderr=OutputStream, stdin=OutputStream) else: pid = subprocess.Popen([sys.executable, modulename, args], stdout=OutputStream, stderr=OutputStream, stdin=OutputStream) return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def UnloadModule(self, modulename, HardStop=False): try: self.LogConsole("Stopping " + modulename) LoadInfo = [] LoadInfo.append('pkill') if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append('-u') LoadInfo.append('root') LoadInfo.append('-f') LoadInfo.append(modulename) process = Popen(LoadInfo, stdout=PIPE) output, _error = process.communicate() rc = process.returncode return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False
class Loader(MySupport): def __init__(self, start = False, stop = False, hardstop = False, loglocation = ProgramDefaults.LogPath, log = None, localinit = False, ConfigFilePath = ProgramDefaults.ConfPath): self.Start = start self.Stop = stop self.HardStop = hardstop self.ConfigFilePath = ConfigFilePath self.ConfigFileName = "genloader.conf" # log errors in this module to a file if localinit == True: self.configfile = self.ConfigFileName else: self.configfile = self.ConfigFilePath + self.ConfigFileName self.ModulePath = os.path.dirname(os.path.realpath(__file__)) + "/" self.ConfPath = os.path.dirname(os.path.realpath(__file__)) + "/conf/" # log errors in this module to a file if log == None: self.log = SetupLogger("genloader", loglocation + "genloader.log") else: self.log = log self.console = SetupLogger("genloader_console", log_file = "", stream = True) try: if self.Start: if not self.CheckSystem(): self.LogInfo("Error check system readiness. Exiting") sys.exit(2) self.CachedConfig = {} if not os.path.isdir(self.ConfigFilePath): try: os.mkdir(self.ConfigFilePath) except Exception as e1: self.LogInfo("Error creating target config directory: " + str(e1), LogLine = True) # check to see if genloader.conf is present, if not copy it from genmon directory if not os.path.isfile(self.configfile): self.LogInfo("Warning: unable to find config file: " + self.configfile + " Copying file to " + self.ConfigFilePath + " directory.") if os.path.isfile(self.ConfPath + self.ConfigFileName): copyfile(self.ConfPath + self.ConfigFileName , self.configfile) else: self.LogInfo("Unable to find config file.") sys.exit(2) self.config = MyConfig(filename = self.configfile, section = "genmon", log = self.log) if not self.GetConfig(): self.LogInfo("Error reading config file. Exiting") sys.exit(2) if not self.ValidateConfig(): self.LogInfo("Error validating config. Exiting") sys.exit(2) self.LoadOrder = self.GetLoadOrder() if self.Stop: self.StopModules() time.sleep(2) if self.Start: self.StartModules() except Exception as e1: self.LogErrorLine("Error in init: " + str(e1)) #--------------------------------------------------------------------------- def CheckSystem(self): # this function checks the system to see if the required libraries are # installed. If they are not then an attempt is made to install them. ModuleList = [ # [import name , install name] ['flask','flask'], ['configparser','configparser'], ['serial','pyserial'], ['crcmod','crcmod'], ['pyowm','pyowm'], ['pytz','pytz'], ['pysnmp','pysnmp'] ] try: ErrorOccured = False for Module in ModuleList: if not self.LibraryIsInstalled(Module[0]): self.LogInfo("Warning: required library " + Module[1] + " not installed. Attempting to install....") if not self.InstallLibrary(Module[1]): self.LogInfo("Error: unable to install library " + Module[1]) ErrorOccured = True return not ErrorOccured except Exception as e1: self.LogInfo("Error in CheckSystem: " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- @staticmethod def OneTimeMaint(ConfigFilePath, log): FileList = { "feedback.json" : os.path.dirname(os.path.realpath(__file__)) + "/", "outage.txt" : os.path.dirname(os.path.realpath(__file__)) + "/", "kwlog.txt" : os.path.dirname(os.path.realpath(__file__)) + "/", "maintlog.json" : os.path.dirname(os.path.realpath(__file__)) + "/", "Feedback_dat" : os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/", "Message_dat" : os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/", 'genmon.conf' : "/etc/", 'genserv.conf': "/etc/", 'gengpio.conf' : "/etc/", 'gengpioin.conf': "/etc/", 'genlog.conf' : "/etc/", 'gensms.conf' : "/etc/", 'gensms_modem.conf': "/etc/", 'genpushover.conf': "/etc/", 'gensyslog.conf' : "/etc/", 'genmqtt.conf' : "/etc/", 'genslack.conf': "/etc/", 'genexercise.conf' : "/etc/", 'genemail2sms.conf' : "/etc/", 'genloader.conf' : "/etc/", 'mymail.conf' : "/etc/", 'mymodem.conf' : "/etc/" } try: # Check to see if we have done this already by checking files in the genmon source directory if (not os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/Message_dat") and not os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + "/maintlog.json") and not os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + "/outage.txt") and not os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + "/kwlog.txt") and not os.path.isfile("/etc/genmon.conf")): return False # validate target directory if not os.path.isdir(ConfigFilePath): try: os.mkdir(ConfigFilePath) if not os.access(ConfigFilePath + File, os.R_OK): pass except Exception as e1: log.error("Error validating target directory: " + str(e1), LogLine = True) # move files for File, Path in FileList.items(): try: SourceFile = Path + File if os.path.isfile(SourceFile): log.error("Moving " + SourceFile + " to " + ConfigFilePath ) if not MySupport.CopyFile(SourceFile, ConfigFilePath + File, move = True, log = log): log.error("Error: using alternate move method") move(SourceFile , ConfigFilePath + File) if not os.access(ConfigFilePath + File, os.R_OK): pass except Exception as e1: log.error("Error moving " + SourceFile) except Exception as e1: log.error("Error moving files: " + str(e1), LogLine = True) return True #--------------------------------------------------------------------------- def LibraryIsInstalled(self, libraryname): try: import importlib my_module = importlib.import_module(libraryname) return True except Exception as e1: return False #--------------------------------------------------------------------------- def InstallLibrary(self, libraryname): try: process = Popen(['pip','install', libraryname], stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in InstallLibrary using pip : " + libraryname + " : " + str(_error)) rc = process.returncode return True except Exception as e1: self.LogInfo("Error installing module: " + libraryname + ": "+ str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def ValidateConfig(self): ErrorOccured = False if not len(self.CachedConfig): self.LogInfo("Error: Empty configruation found.") return False for Module, Settiings in self.CachedConfig.items(): try: if self.CachedConfig[Module]["enable"]: if not os.path.isfile(self.ModulePath + self.CachedConfig[Module]["module"]): self.LogInfo("Enable to find file " + self.ModulePath + self.CachedConfig[Module]["module"]) ErrorOccured = True # validate config file and if it is not there then copy it. if not self.CachedConfig[Module]["conffile"] == None and len(self.CachedConfig[Module]["conffile"]): ConfFileList = self.CachedConfig[Module]["conffile"].split(",") for ConfigFile in ConfFileList: ConfigFile = ConfigFile.strip() if not os.path.isfile(self.ConfigFilePath + ConfigFile): if os.path.isfile(self.ConfPath + ConfigFile): self.LogInfo("Copying " + ConfigFile + " to " + self.ConfigFilePath ) copyfile(self.ConfPath + ConfigFile , self.ConfigFilePath + ConfigFile) else: self.LogInfo("Enable to find config file " + self.ConfPath + ConfigFile) ErrorOccured = True except Exception as e1: self.LogInfo("Error validating config for " + Module + " : " + str(e1), LogLine = True) return False return not ErrorOccured #--------------------------------------------------------------------------- def AddEntry(self, section = None, module = None, conffile = "", args = "", priority = '2'): try: if section == None or module == None: return self.config.WriteSection(section) self.config.WriteValue('module', module, section = section) self.config.WriteValue('enable', 'False', section = section) self.config.WriteValue('hardstop', 'False', section = section) self.config.WriteValue('conffile', conffile, section = section) self.config.WriteValue('args', args, section = section) self.config.WriteValue('priority', priority, section = section) except Exception as e1: self.LogInfo("Error in AddEntry: " + str(e1), LogLine = True) return #--------------------------------------------------------------------------- def UpdateIfNeeded(self): try: self.config.SetSection("gengpioin") if not self.config.HasOption('conffile'): self.config.WriteValue('conffile', "gengpioin.conf", section = "gengpioin") self.LogError("Updated entry gengpioin.conf") else: defValue = self.config.ReadValue('conffile', default = "") if not len(defValue): self.config.WriteValue('conffile', "gengpioin.conf", section = "gengpioin") self.LogError("Updated entry gengpioin.conf") except Exception as e1: self.LogInfo("Error in UpdateIfNeeded: " + str(e1), LogLine = True) #--------------------------------------------------------------------------- def GetConfig(self): try: Sections = self.config.GetSections() ValidSections = ['genmon', 'genserv', 'gengpio', 'gengpioin', 'genlog', 'gensms', 'gensms_modem', 'genpushover', 'gensyslog', 'genmqtt', 'genslack', 'genexercise', 'genemail2sms', 'gentankutil', 'genalexa', 'gensnmp'] for entry in ValidSections: if not entry in Sections: if entry == 'genslack': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'genslack.py', conffile = 'genslack.conf') if entry == 'genexercise': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'genexercise.py', conffile = 'genexercise.conf') if entry == 'genemail2sms': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'genemail2sms.py', conffile = 'genemail2sms.conf') if entry == 'gentankutil': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'gentankutil.py', conffile = 'gentankutil.conf') if entry == 'genalexa': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'genalexa.py', conffile = 'genalexa.conf') if entry == 'gensnmp': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'gensnmp.py', conffile = 'gensnmp.conf') else: self.LogError("Warning: Missing entry: " + entry) self.UpdateIfNeeded() Sections = self.config.GetSections() for SectionName in Sections: TempDict = {} self.config.SetSection(SectionName) if self.config.HasOption('module'): TempDict['module'] = self.config.ReadValue('module') else: TempDict['module'] = None if self.config.HasOption('enable'): TempDict['enable'] = self.config.ReadValue('enable', return_type = bool) else: TempDict['enable'] = False if self.config.HasOption('hardstop'): TempDict['hardstop'] = self.config.ReadValue('hardstop', return_type = bool) else: TempDict['hardstop'] = False if self.config.HasOption('conffile'): TempDict['conffile'] = self.config.ReadValue('conffile') else: TempDict['conffile'] = None if self.config.HasOption('args'): TempDict['args'] = self.config.ReadValue('args') else: TempDict['args'] = None if self.config.HasOption('priority'): TempDict['priority'] = self.config.ReadValue('priority', return_type = int, default = None) else: TempDict['priority'] = None if self.config.HasOption('postloaddelay'): TempDict['postloaddelay'] = self.config.ReadValue('postloaddelay', return_type = int, default = 0) else: TempDict['postloaddelay'] = 0 if self.config.HasOption('pid'): TempDict['pid'] = self.config.ReadValue('pid', return_type = int, default = 0, NoLog = True) else: TempDict['pid'] = 0 self.CachedConfig[SectionName] = TempDict return True except Exception as e1: self.LogInfo("Error parsing config file: " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def ConvertToInt(self, value, default = None): try: return int(str(value)) except: return default #--------------------------------------------------------------------------- def GetLoadOrder(self): LoadOrder = [] LoadDict = {} try: for Module, Settiings in self.CachedConfig.items(): # get the load order of all modules, even if they are disabled # since we need to stop all modules (even disabled ones) if the # conf file changed try: if self.CachedConfig[Module]["priority"] == None: LoadDict[Module] = 99 elif self.CachedConfig[Module]["priority"] >= 0: LoadDict[Module] = self.CachedConfig[Module]["priority"] else: LoadDict[Module] = 99 except Exception as e1: self.LogInfo("Error reading load order (retrying): " + str(e1), LogLine = True) #lambda kv: (-kv[1], kv[0]) for key, value in sorted(LoadDict.items(), key=lambda kv: (-kv[1], kv[0])): #for key, value in sorted(LoadDict.iteritems(), key=lambda (k,v): (v,k)): LoadOrder.append(key) except Exception as e1: self.LogInfo("Error reading load order: " + str(e1), LogLine = True) return LoadOrder #--------------------------------------------------------------------------- def StopModules(self): self.LogConsole("Stopping....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to stop.") return False ErrorOccured = False for Module in self.LoadOrder: try: if not self.UnloadModule(self.ModulePath, self.CachedConfig[Module]["module"], pid = self.CachedConfig[Module]["pid"],HardStop = self.CachedConfig[Module]["hardstop"], UsePID = True): self.LogInfo("Error stopping " + Module) ErrorOccured = True except Exception as e1: self.LogInfo("Error stopping module " + Module + " : " + str(e1), LogLine = True) return False return not ErrorOccured #--------------------------------------------------------------------------- def StartModules(self): self.LogConsole("Starting....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to start.") return False ErrorOccured = False for Module in reversed(self.LoadOrder): try: if self.CachedConfig[Module]["enable"]: if not self.LoadModule(self.ModulePath, self.CachedConfig[Module]["module"], args = self.CachedConfig[Module]["args"]): self.LogInfo("Error starting " + Module) ErrorOccured = True if not self.CachedConfig[Module]["postloaddelay"] == None and self.CachedConfig[Module]["postloaddelay"] > 0: time.sleep(self.CachedConfig[Module]["postloaddelay"]) except Exception as e1: self.LogInfo("Error starting module " + Module + " : " + str(e1), LogLine = True) return False return not ErrorOccured #--------------------------------------------------------------------------- def LoadModuleAlt(self, modulename, args = None): try: self.LogConsole("Starting " + modulename) # to load as a background process we just use os.system since Popen # is problematic in doing this CommandString = sys.executable + " " + modulename if args != None and len(args): CommandString += " " + args CommandString += " &" os.system(CommandString) return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def LoadModule(self, path, modulename, args = None): try: fullmodulename = path + modulename if args != None: self.LogConsole("Starting " + fullmodulename + " " + args) else: self.LogConsole("Starting " + fullmodulename) try: from subprocess import DEVNULL # py3k except ImportError: import os DEVNULL = open(os.devnull, 'wb') if not len(args): args = None if "genserv.py" in modulename: OutputStream = DEVNULL else: OutputStream = subprocess.PIPE executelist = [sys.executable, fullmodulename] if args != None: executelist.extend(args.split(" ")) # This will make all the programs use the same config files executelist.extend(["-c", self.ConfigFilePath]) # close_fds=True pid = subprocess.Popen(executelist, stdout=OutputStream, stderr=OutputStream, stdin=OutputStream) self.UpdatePID(modulename, pid.pid) return True except Exception as e1: self.LogInfo("Error loading module " + modulename + ": " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def UnloadModule(self, path, modulename, pid = None, HardStop = False, UsePID = False): try: LoadInfo = [] if UsePID: if pid == None or pid == "" or pid == 0: return True LoadInfo.append("kill") if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append(str(pid)) else: LoadInfo.append('pkill') if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append('-u') LoadInfo.append('root') LoadInfo.append('-f') LoadInfo.append(modulename) self.LogConsole("Stopping " + modulename) process = Popen(LoadInfo, stdout=PIPE) output, _error = process.communicate() rc = process.returncode self.UpdatePID(modulename, "") return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def UpdatePID(self, modulename, pid = None): try: filename = os.path.splitext(modulename)[0] # remove extension self.config.SetSection(filename) self.config.WriteValue("pid", str(pid)) except Exception as e1: self.LogInfo("Error writing PID for " + modulename + " : " + str(e1))
class Monitor(MySupport): def __init__(self, ConfigFilePath = ProgramDefaults.ConfPath): super(Monitor, self).__init__() self.ProgramName = "Generator Monitor" self.Version = "Unknown" self.log = None self.IsStopping = False self.ProgramComplete = False if ConfigFilePath == None or ConfigFilePath == "": self.ConfigFilePath = ProgramDefaults.ConfPath else: self.ConfigFilePath = ConfigFilePath self.ConnectionList = [] # list of incoming connections for heartbeat # defautl values self.SiteName = "Home" self.ServerSocket = None self.ServerSocketPort = ProgramDefaults.ServerPort # server socket for nagios heartbeat and command/status self.IncomingEmailFolder = "Generator" self.ProcessedEmailFolder = "Generator/Processed" self.FeedbackLogFile = self.ConfigFilePath + "feedback.json" self.LogLocation = ProgramDefaults.LogPath self.LastLogFileSize = 0 self.NumberOfLogSizeErrors = 0 # set defaults for optional parameters self.NewInstall = False # True if newly installed or newly upgraded version self.FeedbackEnabled = False # True if sending autoated feedback on missing information self.FeedbackMessages = {} self.OneTimeMessages = {} self.MailInit = False # set to true once mail is init self.CommunicationsActive = False # Flag to let the heartbeat thread know we are communicating self.Controller = None self.ControllerSelected = None self.bDisablePlatformStats = False self.ReadOnlyEmailCommands = False self.SlowCPUOptimization = False # weather parameters self.WeatherAPIKey = None self.WeatherLocation = None self.UseMetric = False self.WeatherMinimum = True self.DisableWeather = False self.MyWeather = None # Time Sync Related Data self.bSyncTime = False # Sync gen to system time self.bSyncDST = False # sync time at DST change self.bDST = False # Daylight Savings Time active if True # simulation self.Simulation = False self.SimulationFile = None self.console = SetupLogger("genmon_console", log_file = "", stream = True) if os.geteuid() != 0: self.LogConsole("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'.") sys.exit(1) if not os.path.isfile(self.ConfigFilePath + 'genmon.conf'): self.LogConsole("Missing config file : " + self.ConfigFilePath + 'genmon.conf') sys.exit(1) if not os.path.isfile(self.ConfigFilePath + 'mymail.conf'): self.LogConsole("Missing config file : " + self.ConfigFilePath + 'mymail.conf') sys.exit(1) self.config = MyConfig(filename = self.ConfigFilePath + 'genmon.conf', section = "GenMon", log = self.console) # read config file if not self.GetConfig(): self.LogConsole("Failure in Monitor GetConfig") sys.exit(1) # log errors in this module to a file self.log = SetupLogger("genmon", self.LogLocation + "genmon.log") self.config.log = self.log if self.IsLoaded(): self.LogConsole("ERROR: genmon.py is already loaded.") self.LogError("ERROR: genmon.py is already loaded.") sys.exit(1) if self.NewInstall: self.LogError("New version detected: Old = %s, New = %s" % (self.Version, GENMON_VERSION)) self.Version = GENMON_VERSION self.ProgramStartTime = datetime.datetime.now() # used for com metrics self.LastSofwareUpdateCheck = datetime.datetime.now() atexit.register(self.Close) signal.signal(signal.SIGTERM, self.Close) signal.signal(signal.SIGINT, self.Close) # start thread to accept incoming sockets for nagios heartbeat and command / status clients self.Threads["InterfaceServerThread"] = MyThread(self.InterfaceServerThread, Name = "InterfaceServerThread") # init mail, start processing incoming email self.mail = MyMail(monitor=True, incoming_folder = self.IncomingEmailFolder, processed_folder =self.ProcessedEmailFolder,incoming_callback = self.ProcessCommand, loglocation = self.LogLocation, ConfigFilePath = ConfigFilePath) self.Threads = self.MergeDicts(self.Threads, self.mail.Threads) self.MailInit = True self.FeedbackPipe = MyPipe("Feedback", self.FeedbackReceiver, log = self.log, ConfigFilePath = self.ConfigFilePath) self.Threads = self.MergeDicts(self.Threads, self.FeedbackPipe.Threads) self.MessagePipe = MyPipe("Message", self.MessageReceiver, log = self.log, nullpipe = self.mail.DisableSNMP, ConfigFilePath = self.ConfigFilePath) self.Threads = self.MergeDicts(self.Threads, self.MessagePipe.Threads) try: #Starting device connection if self.Simulation: self.LogError("Simulation Running") if not self.ControllerSelected == None and len(self.ControllerSelected): self.LogError("Selected Controller: " + str(self.ControllerSelected)) else: self.ControllerSelected = "generac_evo_nexus" if self.ControllerSelected.lower() == "h_100" : self.Controller = HPanel(self.log, newinstall = self.NewInstall, simulation = self.Simulation, simulationfile = self.SimulationFile, message = self.MessagePipe, feedback = self.FeedbackPipe, config = self.config) else: self.Controller = Evolution(self.log, self.NewInstall, simulation = self.Simulation, simulationfile = self.SimulationFile, message = self.MessagePipe, feedback = self.FeedbackPipe, config = self.config) self.Threads = self.MergeDicts(self.Threads, self.Controller.Threads) except Exception as e1: self.LogErrorLine("Error opening controller device: " + str(e1)) sys.exit(1) self.StartThreads() self.ProcessFeedbackInfo() # send mail to tell we are starting self.MessagePipe.SendMessage("Generator Monitor Starting at " + self.SiteName, "Generator Monitor Starting at " + self.SiteName , msgtype = "info") self.LogError("GenMon Loaded for site: " + self.SiteName) # ------------------------ Monitor::StartThreads---------------------------- def StartThreads(self, reload = False): try: # start thread to accept incoming sockets for nagios heartbeat self.Threads["ComWatchDog"] = MyThread(self.ComWatchDog, Name = "ComWatchDog") if self.bSyncDST or self.bSyncTime: # Sync time thread self.Threads["TimeSyncThread"] = MyThread(self.TimeSyncThread, Name = "TimeSyncThread") if not self.DisableWeather and not self.WeatherAPIKey == None and len(self.WeatherAPIKey) and not self.WeatherLocation == None and len(self.WeatherLocation): Unit = 'metric' if self.UseMetric else 'imperial' self.MyWeather = MyWeather(self.WeatherAPIKey, location = self.WeatherLocation, unit = Unit, log = self.log) self.Threads = self.MergeDicts(self.Threads, self.MyWeather.Threads) except Exception as e1: self.LogErrorLine("Error in StartThreads: " + str(e1)) # -------------------- Monitor::GetConfig----------------------------------- def GetConfig(self): try: if self.config.HasOption('sitename'): self.SiteName = self.config.ReadValue('sitename') if self.config.HasOption('incoming_mail_folder'): self.IncomingEmailFolder = self.config.ReadValue('incoming_mail_folder') # imap folder for incoming mail if self.config.HasOption('processed_mail_folder'): self.ProcessedEmailFolder = self.config.ReadValue('processed_mail_folder') # imap folder for processed mail # server_port, must match value in myclient.py and check_monitor_system.py and any calling client apps if self.config.HasOption('server_port'): self.ServerSocketPort = self.config.ReadValue('server_port', return_type = int) self.LogLocation = self.config.ReadValue('loglocation', default = ProgramDefaults.LogPath) if self.config.HasOption('syncdst'): self.bSyncDST = self.config.ReadValue('syncdst', return_type = bool) if self.config.HasOption('synctime'): self.bSyncTime = self.config.ReadValue('synctime', return_type = bool) if self.config.HasOption('disableplatformstats'): self.bDisablePlatformStats = self.config.ReadValue('disableplatformstats', return_type = bool) if self.config.HasOption('simulation'): self.Simulation = self.config.ReadValue('simulation', return_type = bool) if self.config.HasOption('simulationfile'): self.SimulationFile = self.config.ReadValue('simulationfile') if self.config.HasOption('controllertype'): self.ControllerSelected = self.config.ReadValue('controllertype') if self.config.HasOption('disableweather'): self.DisableWeather = self.config.ReadValue('disableweather', return_type = bool) else: self.DisableWeather = False if self.config.HasOption('weatherkey'): self.WeatherAPIKey = self.config.ReadValue('weatherkey') if self.config.HasOption('weatherlocation'): self.WeatherLocation = self.config.ReadValue('weatherlocation') if self.config.HasOption('metricweather'): self.UseMetric = self.config.ReadValue('metricweather', return_type = bool) if self.config.HasOption('minimumweatherinfo'): self.WeatherMinimum = self.config.ReadValue('minimumweatherinfo', return_type = bool) if self.config.HasOption('readonlyemailcommands'): self.ReadOnlyEmailCommands = self.config.ReadValue('readonlyemailcommands', return_type = bool) if self.config.HasOption('optimizeforslowercpu'): self.SlowCPUOptimization = self.config.ReadValue('optimizeforslowercpu', return_type = bool) if self.config.HasOption('version'): self.Version = self.config.ReadValue('version') if not self.Version == GENMON_VERSION: self.config.WriteValue('version', GENMON_VERSION) self.NewInstall = True else: self.config.WriteValue('version', GENMON_VERSION) self.NewInstall = True self.Version = GENMON_VERSION if self.config.HasOption("autofeedback"): self.FeedbackEnabled = self.config.ReadValue('autofeedback', return_type = bool) else: self.config.WriteValue('autofeedback', "False") self.FeedbackEnabled = False # Load saved feedback log if log is present if os.path.isfile(self.FeedbackLogFile): try: with open(self.FeedbackLogFile) as infile: self.FeedbackMessages = json.load(infile) except Exception as e1: os.remove(self.FeedbackLogFile) self.UpdateCheck = self.config.ReadValue('update_check', return_type = bool, default = True) self.UserURL = self.config.ReadValue('user_url', default = "").strip() except Exception as e1: self.Console("Missing config file or config file entries (genmon): " + str(e1)) return False return True #--------------------------------------------------------------------------- def ProcessFeedbackInfo(self): try: if self.FeedbackEnabled: for Key, Entry in self.FeedbackMessages.items(): self.MessagePipe.SendMessage("Generator Monitor Submission", Entry , recipient = self.MaintainerAddress, files = self.GetLogFileNames(), msgtype = "error") # delete unsent Messages if os.path.isfile(self.FeedbackLogFile): os.remove(self.FeedbackLogFile) except Exception as e1: self.LogErrorLine("Error in ProcessFeedbackInfo: " + str(e1)) #--------------------------------------------------------------------------- def FeedbackReceiver(self, Message): try: FeedbackDict = {} FeedbackDict = json.loads(Message) self.SendFeedbackInfo(FeedbackDict["Reason"], Always = FeedbackDict["Always"], Message = FeedbackDict["Message"], FullLogs = FeedbackDict["FullLogs"], NoCheck = FeedbackDict["NoCheck"]) except Exception as e1: self.LogErrorLine("Error in FeedbackReceiver: " + str(e1)) self.LogError("Size : " + str(len(Message))) self.LogError("Message : " + str(Message)) #--------------------------------------------------------------------------- def MessageReceiver(self, Message): try: MessageDict = {} MessageDict = json.loads(Message) if MessageDict["onlyonce"]: Subject = self.OneTimeMessages.get(MessageDict["subjectstr"], None) if Subject == None: self.OneTimeMessages[MessageDict["subjectstr"]] = MessageDict["msgstr"] else: return self.mail.sendEmail(MessageDict["subjectstr"], MessageDict["msgstr"], recipient = MessageDict["recipient"], files = MessageDict["files"], deletefile= MessageDict["deletefile"], msgtype= MessageDict["msgtype"]) except Exception as e1: self.LogErrorLine("Error in MessageReceiver: " + str(e1)) #--------------------------------------------------------------------------- def SendFeedbackInfo(self, Reason, Always = False, Message = None, FullLogs = True, NoCheck = False): try: if self.NewInstall or Always: CheckedSent = self.FeedbackMessages.get(Reason, "") if not CheckedSent == "" and not NoCheck: return if not NoCheck: self.LogError(Reason + " : " + Message) msgbody = "Reason = " + Reason + "\n" if Message != None: msgbody += "Message : " + Message + "\n" msgbody += self.printToString(self.ProcessDispatch(self.GetStartInfo(NoTile = True),"")) if not self.bDisablePlatformStats: msgbody += self.printToString(self.ProcessDispatch({"Platform Stats" : self.GetPlatformStats()},"")) msgbody += self.Controller.DisplayRegisters(AllRegs = FullLogs) msgbody += "\n" + self.GetSupportData() + "\n" if self.FeedbackEnabled: self.MessagePipe.SendMessage("Generator Monitor Submission", msgbody , recipient = self.MaintainerAddress, files = self.GetLogFileNames(), msgtype = "error") self.FeedbackMessages[Reason] = msgbody # if feedback not enabled, save the log to file if not self.FeedbackEnabled: with open(self.FeedbackLogFile, 'w') as outfile: json.dump(self.FeedbackMessages, outfile, sort_keys = True, indent = 4, ensure_ascii = False) except Exception as e1: self.LogErrorLine("Error in SendFeedbackInfo: " + str(e1)) #---------- Monitor::EmailSendIsEnabled------------------------------------- def EmailSendIsEnabled(self): EmailThread = self.Threads.get("SendMailThread",None) if EmailThread == None: return False return True #---------- Monitor::GetSupportData----------------------------------------- def GetSupportData(self): SupportData = collections.OrderedDict() SupportData["Program Run Time"] = self.GetProgramRunTime() SupportData["Monitor Health"] = self.GetSystemHealth() SupportData["StartInfo"] = self.GetStartInfo(NoTile = True) if not self.bDisablePlatformStats: SupportData["PlatformStats"] = self.GetPlatformStats() SupportData["Data"] = self.Controller.DisplayRegisters(AllRegs = True, DictOut = True) # Raw Modbus data SupportData["Registers"] = self.Controller.Registers SupportData["Strings"] = self.Controller.Strings SupportData["FileData"] = self.Controller.FileData return json.dumps(SupportData, sort_keys=False) #---------- Monitor::GetLogFileNames---------------------------------------- def GetLogFileNames(self): try: LogList = [] FilesToSend = ["genmon.log", "genserv.log", "mymail.log", "myserial.log", "mymodbus.log", "gengpio.log", "gengpioin.log", "gensms.log", "gensms_modem.log", "genmqtt.log", "genpushover.log", "gensyslog.log", "genloader.log", "myserialtcp.log", "genlog.log", "genslack.log", "genexercise.log","genemail2sms.log", "gentankutil.log", "genalexa.log", "gensnmp.log"] for File in FilesToSend: LogFile = self.LogLocation + File if os.path.isfile(LogFile): LogList.append(LogFile) return LogList except Exception as e1: return None #---------- Monitor::SendSupportInfo---------------------------------------- def SendSupportInfo(self, SendLogs = True): try: if not self.EmailSendIsEnabled(): self.LogError("Error in SendSupportInfo: send email is not enabled") return "Send Email is not enabled." msgbody = "" msgbody += self.printToString(self.ProcessDispatch(self.GetStartInfo(NoTile = True),"")) if not self.bDisablePlatformStats: msgbody += self.printToString(self.ProcessDispatch({"Platform Stats" : self.GetPlatformStats()},"")) msgbody += self.Controller.DisplayRegisters(AllRegs = True) msgbody += "\n" + self.GetSupportData() + "\n" msgtitle = "Generator Monitor Log File Submission" if SendLogs == True: LogList = self.GetLogFileNames() else: msgtitle = "Generator Monitor Register Submission" LogList = None self.MessagePipe.SendMessage(msgtitle, msgbody , recipient = self.MaintainerAddress, files = LogList, msgtype = "error") return "Log files submitted" except Exception as e1: self.LogErrorLine("Error in SendSupportInfo: " + str(e1)) #---------- process command from email and socket -------------------------- def ProcessCommand(self, command, fromsocket = False): LocalError = False command = command.decode('utf-8') msgsubject = "Generator Command Response at " + self.SiteName if not fromsocket: msgbody = "\n" else: msgbody = "" if(len(command)) == 0: msgsubject = "Error in Generator Command (Lenght is zero)" msgbody += "Invalid GENERATOR command: zero length command." LocalError = True if not LocalError: if(not command.lower().startswith( 'generator:' )): msgsubject = "Error in Generator Command (command prefix)" msgbody += "Invalid GENERATOR command: all commands must be prefixed by \"generator: \"" LocalError = True if LocalError: if not fromsocket: self.MessagePipe.SendMessage(msgsubject, msgbody, msgtype = "error") return "" # ignored by email module else: msgbody += "EndOfMessage" return msgbody if command.lower().startswith('generator:'): command = command[len('generator:'):] CommandDict = { "registers" : [self.Controller.DisplayRegisters,(False,), False], # display registers "allregs" : [self.Controller.DisplayRegisters, (True,), False], # display registers "logs" : [self.Controller.DisplayLogs, (True, False), False], "status" : [self.Controller.DisplayStatus, (), False], # display decoded generator info "maint" : [self.Controller.DisplayMaintenance, (), False], "monitor" : [self.DisplayMonitor, (), False], "outage" : [self.Controller.DisplayOutage, (), False], "settime" : [self.StartTimeThread, (), False], # set time and date "setexercise" : [self.Controller.SetGeneratorExerciseTime, (command.lower(),), False], "setquiet" : [self.Controller.SetGeneratorQuietMode, ( command.lower(),), False], "setremote" : [self.Controller.SetGeneratorRemoteCommand, (command.lower(),), False], "testcommand" : [self.Controller.TestCommand, (command.lower(),), False], "network_status": [self.InternetConnected, (), False], "help" : [self.DisplayHelp, (), False], # display help screen ## These commands are used by the web / socket interface only "power_log_json" : [self.Controller.GetPowerHistory, (command.lower(),), True], "power_log_clear" : [self.Controller.ClearPowerLog, (), True], "start_info_json" : [self.GetStartInfo, (), True], "registers_json" : [self.Controller.DisplayRegisters, (False, True), True], # display registers "allregs_json" : [self.Controller.DisplayRegisters, (True, True), True], # display registers "logs_json" : [self.Controller.DisplayLogs, (True, True), True], "status_json" : [self.Controller.DisplayStatus, (True,), True], "status_num_json" : [self.Controller.DisplayStatus, (True,True), True], "maint_json" : [self.Controller.DisplayMaintenance, (True,), True], "maint_num_json" : [self.Controller.DisplayMaintenance, (True,True), True], "monitor_json" : [self.DisplayMonitor, (True,), True], "monitor_num_json" : [self.DisplayMonitor, (True,True), True], "weather_json" : [self.DisplayWeather, (True,), True], "outage_json" : [self.Controller.DisplayOutage, (True,), True], "outage_num_json" : [self.Controller.DisplayOutage, (True,True), True], "gui_status_json" : [self.GetStatusForGUI, (), True], "get_maint_log_json": [self.Controller.GetMaintLog, (), True], "add_maint_log" : [self.Controller.AddEntryToMaintLog, (command,), True], # Do not do command.lower() since this input is JSON "clear_maint_log" : [self.Controller.ClearMaintLog, (), True], "getsitename" : [self.GetSiteName, (), True], "getbase" : [self.Controller.GetBaseStatus, (), True], # (UI changes color based on exercise, running , ready status) "gethealth" : [self.GetSystemHealth, (), True], "getregvalue" : [self.Controller.GetRegValue, (command.lower(),), True], # only used for debug purposes, read a cached register value "readregvalue" : [self.Controller.ReadRegValue, (command.lower(),), True], # only used for debug purposes, Read Register Non Cached "getdebug" : [self.GetDeadThreadName, (), True], # only used for debug purposes. If a thread crashes it tells you the thread name "sendregisters" : [self.SendSupportInfo, (False,), True], "sendlogfiles" : [self.SendSupportInfo, (True,), True], "set_tank_data" : [self.Controller.SetExternalTankData, (command,), True] } CommandList = command.split(' ') ValidCommand = False try: for item in CommandList: if not len(item): continue item = item.strip() LookUp = item if "=" in item: BaseCmd = item.split('=') LookUp = BaseCmd[0] # check if we disallow write commands via email if self.ReadOnlyEmailCommands and not fromsocket and LookUp in ["settime", "setexercise", "setquiet", "setremote"]: continue ExecList = CommandDict.get(LookUp.lower(),None) if ExecList == None: continue if ExecList[0] == None: continue if not fromsocket and ExecList[2]: continue # Execut Command ReturnMessage = ExecList[0](*ExecList[1]) ValidCommand = True if LookUp.lower().endswith("_json") and not isinstance(ReturnMessage, str): msgbody += json.dumps(ReturnMessage, sort_keys=False) else: msgbody += ReturnMessage if not fromsocket: msgbody += "\n" except Exception as e1: self.LogErrorLine("Error Processing Commands: " + command + ": "+ str(e1)) if not ValidCommand: msgbody += "No valid command recognized." if not fromsocket: self.MessagePipe.SendMessage(msgsubject, msgbody, msgtype = "warn") return "" # ignored by email module else: msgbody += "EndOfMessage" return msgbody #------------ Monitor::DisplayHelp ----------------------------------------- def DisplayHelp(self): outstring = "" outstring += "Help:\n" outstring += self.printToString("\nCommands:") outstring += self.printToString(" status - display engine and line information") outstring += self.printToString(" maint - display maintenance and service information") outstring += self.printToString(" outage - display current and last outage (since program launched)") outstring += self.printToString(" info, also shows utility min and max values") outstring += self.printToString(" monitor - display communication statistics and monitor health") outstring += self.printToString(" logs - display all alarm, on/off, and maintenance logs") outstring += self.printToString(" registers - display contents of registers being monitored") outstring += self.printToString(" settime - set generator time to system time") outstring += self.printToString(" setexercise - set the exercise time of the generator. ") outstring += self.printToString(" i.e. setexercise=Monday,13:30,Weekly") outstring += self.printToString(" if Enhanced Exercise Frequency is supported by your generator:") outstring += self.printToString(" i.e. setexercise=Monday,13:30,BiWeekly") outstring += self.printToString(" i.e. setexercise=15,13:30,Monthly") outstring += self.printToString(" setquiet - enable or disable exercise quiet mode, ") outstring += self.printToString(" i.e. setquiet=on or setquiet=off") outstring += self.printToString(" setremote - issue remote command. format is setremote=command, ") outstring += self.printToString(" where command is start, stop, starttransfer,") outstring += self.printToString(" startexercise. i.e. setremote=start") outstring += self.printToString(" help - Display help on commands") outstring += self.printToString("\n") outstring += self.printToString("To clear the Alarm/Warning message, press OFF on the control panel keypad") outstring += self.printToString("followed by the ENTER key. To access Dealer Menu on the Evolution") outstring += self.printToString("controller, from the top menu selection (SYSTEM, DATE/TIME,BATTERY, SUB-MENUS)") outstring += self.printToString("enter UP UP ESC DOWN UP ESC UP, then go to the dealer menu and press enter.") outstring += self.printToString("For liquid cooled models a level 2 dealer code can be entered, ESC UP UP DOWN") outstring += self.printToString("DOWN ESC ESC, then navigate to the dealer menu and press enter.") outstring += self.printToString("Passcode for Nexus controller is ESC, UP, UP ESC, DOWN, UP, ESC, UP, UP, ENTER.") outstring += self.printToString("\n") return outstring #------------ Monitor::GetProgramRunTime ----------------------------------- def GetProgramRunTime(self): try: ProgramRunTime = datetime.datetime.now() - self.ProgramStartTime outstr = str(ProgramRunTime).split(".")[0] # remove microseconds from string return self.ProgramName + " running for " + outstr + "." except Exception as e1: self.LogErrorLine("Error in GetProgramRunTime:" + str(e1)) return "Unknown" #------------ Monitor::GetWeatherData -------------------------------------- def GetWeatherData(self, ForUI = False): if self.MyWeather == None: return None ReturnData = self.MyWeather.GetWeather(minimum = self.WeatherMinimum, ForUI = ForUI) if not len(ReturnData): return None return ReturnData #------------ Monitor::GetUserDefinedData ---------------------------------- # this assumes one json object, the file can be formatted (i.e. on multiple # lines) or can be on a single line def GetUserDefinedData(self): try: FileName = os.path.dirname(os.path.realpath(__file__)) + "/userdefined.json" if not os.path.isfile(FileName): return None if os.path.getsize(FileName) == 0: return None with open(FileName) as f: data = json.load(f,object_pairs_hook=collections.OrderedDict) return data except Exception as e1: self.LogErrorLine("Error in GetUserDefinedData: " + str(e1)) return None #------------ Monitor::DisplayWeather -------------------------------------- def DisplayWeather(self, DictOut = False): WeatherData = collections.OrderedDict() try: ReturnData = self.GetWeatherData() if not ReturnData == None and len(ReturnData): WeatherData["Weather"] = ReturnData if not DictOut: return self.printToString(self.ProcessDispatch(WeatherData,"")) except Exception as e1: self.LogErrorLine("Error in DisplayWeather: " + str(e1)) return WeatherData #------------ Monitor::DisplayMonitor -------------------------------------- def DisplayMonitor(self, DictOut = False, JSONNum = False): try: Monitor = collections.OrderedDict() MonitorData = [] Monitor["Monitor"] = MonitorData GenMonStats = [] SerialStats = [] MonitorData.append({"Generator Monitor Stats" : GenMonStats}) MonitorData.append({"Communication Stats" : self.Controller.GetCommStatus()}) GenMonStats.append({"Monitor Health" : self.GetSystemHealth()}) GenMonStats.append({"Controller" : self.Controller.GetController(Actual = False)}) GenMonStats.append({"Run time" : self.GetProgramRunTime()}) if self.Controller.PowerMeterIsSupported(): GenMonStats.append({"Power log file size" : self.Controller.GetPowerLogFileDetails()}) GenMonStats.append({"Generator Monitor Version" : GENMON_VERSION}) if not self.bDisablePlatformStats: PlatformStats = self.GetPlatformStats() if not PlatformStats == None: MonitorData.append({"Platform Stats" : PlatformStats}) WeatherData = self.GetWeatherData() if not WeatherData == None and len(WeatherData): MonitorData.append({"Weather" : WeatherData}) UserData = self.GetUserDefinedData() if not UserData == None and len(UserData): MonitorData.append({"External Data" : UserData}) if not DictOut: return self.printToString(self.ProcessDispatch(Monitor,"")) except Exception as e1: self.LogErrorLine("Error in DisplayMonitor: " + str(e1)) return Monitor #------------ Monitor::GetStartInfo----------------------------------------- def GetStartInfo(self, NoTile = False): StartInfo = collections.OrderedDict() StartInfo["version"] = GENMON_VERSION StartInfo["sitename"] = self.SiteName ControllerStartInfo = self.Controller.GetStartInfo(NoTile = NoTile) StartInfo = self.MergeDicts(StartInfo, ControllerStartInfo) return StartInfo #------------ Monitor::GetStatusForGUI ------------------------------------- def GetStatusForGUI(self): Status = {} Status["SystemHealth"] = self.GetSystemHealth() Status["UnsentFeedback"] = str(os.path.isfile(self.FeedbackLogFile)) if not self.bDisablePlatformStats: PlatformStats = self.GetPlatformStats(usemetric = True) if not PlatformStats == None and len(PlatformStats): Status["PlatformStats"] = PlatformStats WeatherData = self.GetWeatherData(ForUI = True) if not WeatherData == None and len(WeatherData): Status["Weather"] = WeatherData # Monitor Time Status["MonitorTime"] = datetime.datetime.now().strftime("%m/%d/%Y %H:%M") # Engine run hours Status["RunHours"] = self.Controller.GetRunHours() ReturnDict = self.MergeDicts(Status, self.Controller.GetStatusForGUI()) return ReturnDict #-------------Monitor::GetSystemHealth-------------------------------------- # returns the health of the monitor program def GetSystemHealth(self): outstr = "" if not self.Controller.InitComplete: outstr += "System Initializing. " if not self.AreThreadsAlive(): outstr += " Threads are dead. " if not self.CommunicationsActive: outstr += " Not receiving data. " if not self.LogFileIsOK(): outstr += " Log file is reporting errors." if len(outstr) == 0: outstr = "OK" return outstr #---------- Monitor::StartTimeThread--------------------------------------- def StartTimeThread(self): # This is done is a separate thread as not to block any return email processing # since we attempt to sync with generator time MyThread(self.Controller.SetGeneratorTimeDate, Name = "SetTimeThread") return "Time Set: Command Sent\n" #---------- Monitor::TimeSyncThread---------------------------------------- def TimeSyncThread(self): self.bDST = self.is_dst() # set initial DST state time.sleep(0.25) while True: if self.WaitForExit("TimeSyncThread", 1): # ten min return if self.Controller.InitComplete: break # if we are not always syncing, then set the time once if not self.bSyncTime: self.StartTimeThread() while True: if self.bSyncDST: if self.bDST != self.is_dst(): # has DST changed? self.bDST = self.is_dst() # update Flag # time changed so some comm stats may be off self.Controller.ResetCommStats() # set new time self.StartTimeThread() # start settime thread self.MessagePipe.SendMessage("Generator Time Update at " + self.SiteName, "Time updated due to daylight savings time change", msgtype = "info") if self.bSyncTime: # update gen time self.StartTimeThread() if self.WaitForExit("TimeSyncThread", 60 * 60): # 1 hour return #---------- Monitor::is_dst------------------------------------------------ def is_dst(self): #Determine whether or not Daylight Savings Time (DST) is currently in effect t = time.localtime() isdst = t.tm_isdst return (isdst != 0) #---------- Monitor::ComWatchDog------------------------------------------- #---------- monitors receive data status to make sure we are still communicating def ComWatchDog(self): self.CommunicationsActive = False time.sleep(0.25) NoticeSent = False LastActiveTime = datetime.datetime.now() while True: if self.WaitForExit("ComWatchDog", 1): return if self.Controller.InitComplete: break if self.Controller.ModBus.UseTCP: WatchDogPollTime = 8 else: WatchDogPollTime = 2 while True: try: # check for software update self.CheckSoftwareUpdate() self.CommunicationsActive = self.Controller.ComminicationsIsActive() if self.CommunicationsActive: LastActiveTime = datetime.datetime.now() if NoticeSent: NoticeSent = False self.MessagePipe.SendMessage("Generator Monitor Communication Restored at " + self.SiteName, "Generator Monitor communications with the controller has been restored at " + self.SiteName , msgtype = "info") else: if self.GetDeltaTimeMinutes(datetime.datetime.now() - LastActiveTime) > 1 : if not NoticeSent: NoticeSent = True self.MessagePipe.SendMessage("Generator Monitor Communication WARNING at " + self.SiteName, "Generator Monitor is not communicating with the controller at " + self.SiteName , msgtype = "error") except Exception as e1: self.LogErrorLine("Error in ComWatchDog: " + str(e1)) if self.WaitForExit("ComWatchDog", WatchDogPollTime): return #---------- Monitor::CheckSoftwareUpdate------------------------------------ def CheckSoftwareUpdate(self): if not self.UpdateCheck: return try: if self.GetDeltaTimeMinutes(datetime.datetime.now() - self.LastSofwareUpdateCheck) > 1440 : # check every day self.LastSofwareUpdateCheck = datetime.datetime.now() # Do the check try: import urllib2 url = "https://raw.githubusercontent.com/jgyates/genmon/master/genmon.py" data = urllib2.urlopen(url).read(20000) # read only 2000 chars data = data.split("\n") # then split it into lines for line in data: if 'GENMON_VERSION = "V' in line: import re quoted = re.compile('"([^"]*)"') for value in quoted.findall(line): if value != GENMON_VERSION: # Update Available title = self.ProgramName + " Software Update " + value + " is available for site " + self.SiteName msgbody = "\nA software update is available for the " + self.ProgramName + ". The new version (" + value + ") can be updated on the About page of the web interface. You can disable this email from being sent on the Settings page." if len(self.UserURL): msgbody += "\n\nWeb Interface URL: " + self.UserURL msgbody += "\n\nChange Log: https://raw.githubusercontent.com/jgyates/genmon/master/changelog.md" self.MessagePipe.SendMessage(title , msgbody, msgtype = "info", onlyonce = True) except Exception as e1: self.LogErrorLine("Error checking for software update: " + str(e1)) except Exception as e1: self.LogErrorLine("Error in CheckSoftwareUpdate: " + str(e1)) #---------- Monitor::LogFileIsOK-------------------------------------------- def LogFileIsOK(self): try: if not self.Controller.InitComplete: return True LogFile = self.LogLocation + "genmon.log" LogFileSize = os.path.getsize(LogFile) if LogFileSize <= self.LastLogFileSize: # log is unchanged or has rotated self.LastLogFileSize = LogFileSize self.NumberOfLogSizeErrors = 0 return True LogFileSizeDiff = LogFileSize - self.LastLogFileSize self.LastLogFileSize = LogFileSize if LogFileSizeDiff >= 100: self.NumberOfLogSizeErrors += 1 if self.NumberOfLogSizeErrors > 3: return False else: self.NumberOfLogSizeErrors = 0 return True except Exception as e1: self.LogErrorLine("Error in LogFileIsOK: " + str(e1)) return True #---------- Monitor::SocketWorkThread------------------------------------- # This thread spawns for each connection established by a client # in InterfaceServerThread def SocketWorkThread(self, conn): try: statusstr = "" if self.Controller == None: outstr = "WARNING: System Initializing" conn.sendall(outstr.encode()) else: if self.Controller.SystemInAlarm(): statusstr += "CRITICAL: System in alarm! " HealthStr = self.GetSystemHealth() if HealthStr != "OK": statusstr += "WARNING: " + HealthStr if statusstr == "": statusstr = "OK " outstr = statusstr + ": "+ self.Controller.GetOneLineStatus() conn.sendall(outstr.encode()) while True: try: data = conn.recv(1024) if len(data): if self.Controller == None: outstr = "Retry, System Initializing" else: outstr = self.ProcessCommand(data, True) conn.sendall(outstr.encode("utf-8")) else: # socket closed remotely break except socket.timeout: if self.IsStopping: break continue except socket.error as msg: try: self.ConnectionList.remove(conn) conn.close() except: pass break except socket.error as msg: self.LogError("Error in SocketWorkThread: " + str(msg)) pass try: self.ConnectionList.remove(conn) conn.close() except: pass # end SocketWorkThread #---------- interface for heartbeat server thread ------------------------- def InterfaceServerThread(self): #create an INET, STREAMing socket self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # set some socket options so we can resuse the port self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ServerSocket.settimeout(.5) #bind the socket to a host, and a port self.ServerSocket.bind(('', self.ServerSocketPort)) #become a server socket self.ServerSocket.listen(5) #wait to accept a connection - blocking call while True: try: conn, addr = self.ServerSocket.accept() #self.LogError('Connected with ' + addr[0] + ':' + str(addr[1])) conn.settimeout(0.5) self.ConnectionList.append(conn) SocketThread = threading.Thread(target=self.SocketWorkThread, args = (conn,), name = "SocketWorkThread") SocketThread.daemon = True SocketThread.start() # start server thread except socket.timeout: if self.IsStopping: break continue except Exception as e1: if self.IsStopping: break self.LogErrorLine("Exception in InterfaceServerThread" + str(e1)) if self.WaitForExit("InterfaceServerThread", 0.5 ): break continue if self.ServerSocket != None: if len(self.ConnectionList): try: self.ServerSocket.shutdown(socket.SHUT_RDWR) except: pass self.ServerSocket.close() self.ServerSocket = None # #---------------------Monitor::Close---------------------------------------- def Close(self): # we dont really care about the errors that may be generated on shutdown try: self.IsStopping = True try: if self.MyWeather != None: self.MyWeather.Close() except: pass try: if self.bSyncDST or self.bSyncTime: self.KillThread("TimeSyncThread") except: pass try: self.KillThread("ComWatchDog") except: pass try: if not self.Controller == None: self.Controller.Close() except: pass # try: self.mail.Close() except: pass try: for item in self.ConnectionList: try: item.close() except: continue self.ConnectionList.remove(item) except: pass try: if(self.ServerSocket != None): self.ServerSocket.shutdown(socket.SHUT_RDWR) self.ServerSocket.close() self.KillThread("InterfaceServerThread") except: pass try: self.FeedbackPipe.Close() except: pass try: self.MessagePipe.Close() except: pass # Tell any remaining threads to stop for name, object in self.Threads.items(): try: if self.Threads[name].IsAlive(): self.Threads[name].Stop() except Exception as e1: self.LogErrorLine("Error killing thread in Monitor Close: " + name + ":" + str(e1)) except Exception as e1: self.LogErrorLine("Error Closing Monitor: " + str(e1)) with self.CriticalLock: self.LogError("Generator Monitor Shutdown") try: self.ProgramComplete = True sys.exit(0) except: pass
class GenExercise(MySupport): #------------ GenExercise::init--------------------------------------------- def __init__(self, log=None, loglocation=ProgramDefaults.LogPath, ConfigFilePath=MyCommon.DefaultConfPath, host=ProgramDefaults.LocalHost, port=ProgramDefaults.ServerPort, console=None): super(GenExercise, self).__init__() self.AccessLock = threading.Lock() self.log = log self.console = console self.MonitorAddress = host self.PollTime = 2 self.ExerciseActive = False self.Debug = False try: self.config = MyConfig(filename=os.path.join( ConfigFilePath, 'genexercise.conf'), section='genexercise', log=self.log) self.ExerciseType = self.config.ReadValue('exercise_type', default="Normal") self.ExerciseHour = self.config.ReadValue('exercise_hour', return_type=int, default=12) self.ExerciseMinute = self.config.ReadValue('exercise_minute', return_type=int, default=0) self.ExerciseDayOfMonth = self.config.ReadValue( 'exercise_day_of_month', return_type=int, default=1) self.ExerciseDayOfWeek = self.config.ReadValue( 'exercise_day_of_week', default="Monday") self.ExerciseDuration = self.config.ReadValue('exercise_duration', return_type=float, default=12) self.ExerciseWarmup = self.config.ReadValue('exercise_warmup', return_type=float, default=0) self.ExerciseFrequency = self.config.ReadValue( 'exercise_frequency', default="Monthly") self.MonitorAddress = self.config.ReadValue( 'monitor_address', default=ProgramDefaults.LocalHost) self.LastExerciseTime = self.config.ReadValue('last_exercise', default=None) self.UseGeneratorTime = self.config.ReadValue('use_gen_time', return_type=bool, default=False) self.Debug = self.config.ReadValue('debug', return_type=bool, default=False) # Validate settings if not self.ExerciseType.lower() in [ "normal", "quiet", "transfer" ]: self.ExerciseType = "normal" if self.ExerciseHour > 23 or self.ExerciseHour < 0: self.ExerciseHour = 12 if self.ExerciseMinute > 59 or self.ExerciseMinute < 0: self.ExerciseMinute = 0 if not self.ExerciseDayOfWeek.lower() in [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday" ]: self.ExerciseDayOfWeek = "Monday" if self.ExerciseDayOfMonth > 28 or self.ExerciseDayOfMonth < 1: self.ExerciseDayOfMonth = 1 if self.ExerciseDuration > 60: self.ExerciseDuration = 60 if self.ExerciseDuration < 5: self.ExerciseDuration = 5 if self.ExerciseWarmup > 30: self.ExerciseWarmup = 30 if self.ExerciseWarmup < 0: self.ExerciseWarmup = 0 if not self.ExerciseFrequency.lower() in [ "weekly", "biweekly", "monthly" ]: self.ExerciseFrequency = "Monthly" if self.MonitorAddress == None or not len(self.MonitorAddress): self.MonitorAddress = ProgramDefaults.LocalHost except Exception as e1: self.LogErrorLine( "Error reading " + os.path.join(ConfigFilePath, "genexercise.conf") + ": " + str(e1)) self.console.error( "Error reading " + os.path.join(ConfigFilePath, "genexercise.conf") + ": " + str(e1)) sys.exit(1) try: self.Generator = ClientInterface(host=self.MonitorAddress, port=port, log=self.log) if not self.CheckGeneratorRequirement(): self.LogError("Requirements not met. Exiting.") sys.exit(1) # start thread monitor time for exercise self.Threads["ExerciseThread"] = MyThread(self.ExerciseThread, Name="ExerciseThread", start=False) self.Threads["ExerciseThread"].Start() try: if self.ExerciseFrequency.lower() == "monthly": DayStr = "Day " + str(self.ExerciseDayOfMonth) else: DayStr = str(self.ExerciseDayOfWeek) self.LogError("Execise: " + self.ExerciseType + ", " + self.ExerciseFrequency + " at " + str(self.ExerciseHour) + ":" + str(self.ExerciseMinute) + " on " + DayStr + " for " + str(self.ExerciseDuration) + " min. Warmup: " + str(self.ExerciseWarmup)) self.DebugOutput("Debug Enabled") except Exception as e1: self.LogErrorLine(str(e1)) signal.signal(signal.SIGTERM, self.SignalClose) signal.signal(signal.SIGINT, self.SignalClose) except Exception as e1: self.LogErrorLine("Error in GenExercise init: " + str(e1)) self.console.error("Error in GenExercise init: " + str(e1)) sys.exit(1) #---------- GenExercise::SendCommand -------------------------------------- def SendCommand(self, Command): if len(Command) == 0: return "Invalid Command" try: with self.AccessLock: data = self.Generator.ProcessMonitorCommand(Command) except Exception as e1: self.LogErrorLine("Error calling ProcessMonitorCommand: " + str(Command)) data = "" return data #---------- GenExercise::CheckGeneratorRequirement ------------------------ def CheckGeneratorRequirement(self): try: data = self.SendCommand("generator: start_info_json") StartInfo = {} StartInfo = json.loads(data) if not "evolution" in StartInfo["Controller"].lower( ) and not "nexus" in StartInfo["Controller"].lower(): self.LogError( "Error: Only Evolution or Nexus controllers are supported for this feature: " + StartInfo["Controller"]) return False return True except Exception as e1: self.LogErrorLine("Error in CheckGeneratorRequirement: " + str(e1)) return False # ---------- GenExercise::PostWarmup---------------------------------------- def PostWarmup(self): # check to see if the generator is running status = self.SendCommand("generator: getbase") if not status.lower() in ["running", "exercising"]: self.LogError( "WARNING: generator not running post warmup. Transfer switch not activated." ) self.SendCommand("generator: setremote=stop") return self.SendCommand("generator: setremote=starttransfer") self.DebugOutput("Starting transfer exercise cycle (post warmup).") # set timer to stop self.StopTimer = threading.Timer(float(self.ExerciseDuration * 60.0), self.StopExercise) self.StopTimer.start() # ---------- GenExercise::ReadyToExercise----------------------------------- def ReadyToExercise(self): status = self.SendCommand("generator: getbase") if not status.lower() in ["ready", "servicedue"]: self.LogError( "Generator not in Ready state, exercise cycle not started: " + str(status)) return False return True # ---------- GenExercise::StartExercise------------------------------------- def StartExercise(self): if self.ExerciseActive: # already active return # Start generator if self.ExerciseType.lower() == "normal" and self.ReadyToExercise(): self.SendCommand("generator: setremote=start") self.DebugOutput("Starting normal exercise cycle.") self.StopTimer = threading.Timer( float(self.ExerciseDuration * 60.0), self.StopExercise) self.StopTimer.start() elif self.ExerciseType.lower() == "quiet" and self.ReadyToExercise(): self.SendCommand("generator: setremote=startexercise") self.DebugOutput("Starting quiet exercise cycle.") self.StopTimer = threading.Timer( float(self.ExerciseDuration * 60.0), self.StopExercise) self.StopTimer.start() elif self.ExerciseType.lower() == "transfer" and self.ReadyToExercise( ): if self.ExerciseWarmup == 0: self.SendCommand("generator: setremote=starttransfer") self.DebugOutput("Starting transfer exercise cycle.") self.StopTimer = threading.Timer( float(self.ExerciseDuration * 60.0), self.StopExercise) self.StopTimer.start() else: self.SendCommand("generator: setremote=start") self.DebugOutput( "Starting warmup for transfer exercise cycle.") # start timer for post warmup transition to starttransfer command self.WarmupTimer = threading.Timer( float(self.ExerciseWarmup * 60.0), self.PostWarmup) self.WarmupTimer.start() else: self.LogError("Invalid mode in StartExercise: " + str(self.ExerciseType)) return self.WriteLastExerciseTime() self.ExerciseActive = True # ---------- GenExercise::StopExercise-------------------------------------- def StopExercise(self): if self.ExerciseActive: self.SendCommand("generator: setremote=stop") self.DebugOutput("Stopping exercise cycle.") self.ExerciseActive = False else: self.DebugOutput("Calling Stop Exercise (not needed)") # ---------- GenExercise::DebugOutput----------------------------- def DebugOutput(self, Message): if self.Debug: self.LogError(Message) # ---------- GenExercise::WriteLastExerciseTime----------------------------- def WriteLastExerciseTime(self): try: NowString = datetime.datetime.now().strftime( "%A %B %d, %Y %H:%M:%S") if self.ExerciseFrequency.lower() == "biweekly": self.config.WriteValue("last_exercise", NowString) self.config.LastExerciseTime = NowString self.DebugOutput("Last Exercise Cycle: " + NowString) except Exception as e1: self.LogErrorLine("Error in WriteLastExerciseTime: " + str(e1)) # ---------- GenExercise::TimeForExercise----------------------------------- def TimeForExercise(self): try: if self.UseGeneratorTime: TimeNow = self.GetGeneratorTime() else: TimeNow = datetime.datetime.now() if TimeNow.hour != self.ExerciseHour or TimeNow.minute != self.ExerciseMinute: return False weekDays = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") WeekDayString = weekDays[TimeNow.weekday()] if not self.ExerciseFrequency.lower() in [ "weekly", "biweekly", "monthly" ]: self.LogError( "Invalid Exercise Frequency in TimeForExercise: " + str(self.ExerciseFrequency)) return False if self.ExerciseFrequency.lower( ) == "weekly" and self.ExerciseDayOfWeek.lower( ) == WeekDayString.lower(): return True elif self.ExerciseFrequency.lower( ) == "biweekly" and self.ExerciseDayOfWeek.lower( ) == WeekDayString.lower(): if self.LastExerciseTime == None: return True LastExerciseTime = datetime.datetime.strptime( self.LastExerciseTime, "%A %B %d, %Y %H:%M:%S") if (TimeNow - LastExerciseTime).days >= 14: return True return False elif self.ExerciseFrequency.lower( ) == "monthly" and TimeNow.day == self.ExerciseDayOfMonth: return True else: return False except Exception as e1: self.LogErrorLine("Error in TimeForExercise: " + str(e1)) return False # ---------- GenExercise::GetGeneratorTime---------------------------------- def GetGeneratorTime(self): try: GenTimeStr = "" data = self.SendCommand("generator: status_json") Status = {} Status = json.loads(data) TimeDict = self.FindDictValueInListByKey("Time", Status["Status"]) if TimeDict != None: TimeDictStr = self.FindDictValueInListByKey( "Generator Time", TimeDict) if TimeDictStr != None or not len(TimeDictStr): GenTimeStr = TimeDictStr # Format is "Wednesday March 6, 2019 13:10" or " "Friday May 3, 2019 11:11" GenTime = datetime.datetime.strptime( GenTimeStr, "%A %B %d, %Y %H:%M") else: self.LogError( "Error getting generator time! Genmon may be starting up." ) GenTime = datetime.datetime.now() else: self.LogError("Error getting generator time (2)!") GenTime = datetime.datetime.now() return GenTime except Exception as e1: self.LogErrorLine("Error in GetGeneratorTime: " + str(e1) + ": " + GenTimeStr) return datetime.datetime.now() # ---------- GenExercise::ExerciseThread------------------------------------ def ExerciseThread(self): time.sleep(1) while True: try: if not self.ExerciseActive: if self.TimeForExercise(): self.StartExercise() if self.WaitForExit("ExerciseThread", float(self.PollTime)): return except Exception as e1: self.LogErrorLine("Error in ExerciseThread: " + str(e1)) if self.WaitForExit("ExerciseThread", float(self.PollTime)): return # ----------GenExercise::SignalClose---------------------------------------- def SignalClose(self, signum, frame): self.Close() sys.exit(1) # ----------GenExercise::Close---------------------------------------------- def Close(self): self.KillThread("ExerciseThread") if self.ExerciseActive: try: self.WarmupTimer.cancel() except: pass try: self.StopTimer.cancel() except: pass self.StopExercise() self.Generator.Close()
print("updated calibration table:") show_ap_table() else: print("Index must be from 1 to {0}, try again".format( len(gauge.ap_table))) elif c == '6': if gauge.ap_table != None: if len(gauge.ap_table) < 2: print( "must have at least 2 entries in calibration table, file not written" ) else: for i in range(1, gauge.MAX_AP_TABLE + 1): config.WriteValue("ang_pct_pnt_{0}".format(i), "", remove=True) i = 1 for a, p in gauge.ap_table: config.WriteValue("ang_pct_pnt_{0}".format(i), "{0:8.2f},{1:6.1f}".format(a, p)) i += 1 print("wrote {0} calibration entries in '{1}'".format( len(gauge.ap_table), configfile)) elif c == '7': a = gauge.read_gauge_angle() print("Current dial angle is {0:.2f} degrees which is {1:.1f}%". format(a, gauge.convert_angle_to_percent(a)))
class Loader(MySupport): def __init__(self, start=False, stop=False, hardstop=False, loglocation=ProgramDefaults.LogPath, log=None, localinit=False, ConfigFilePath=ProgramDefaults.ConfPath): self.Start = start self.Stop = stop self.HardStop = hardstop self.PipChecked = False self.NewInstall = False self.Upgrade = False self.version = None self.ConfigFilePath = ConfigFilePath self.ConfigFileName = "genloader.conf" # log errors in this module to a file if localinit == True: self.configfile = self.ConfigFileName else: self.configfile = os.path.join(self.ConfigFilePath, self.ConfigFileName) self.ModulePath = os.path.dirname(os.path.realpath(__file__)) self.ConfPath = os.path.join( os.path.dirname(os.path.realpath(__file__)), "conf") # log errors in this module to a file if log == None: self.log = SetupLogger("genloader", os.path.join(loglocation, "genloader.log")) else: self.log = log self.console = SetupLogger("genloader_console", log_file="", stream=True) try: if self.Start: if not self.CheckSystem(): self.LogInfo("Error check system readiness. Exiting") sys.exit(2) self.CachedConfig = {} if not os.path.isdir(self.ConfigFilePath): try: os.mkdir(self.ConfigFilePath) except Exception as e1: self.LogInfo("Error creating target config directory: " + str(e1), LogLine=True) # check to see if genloader.conf is present, if not copy it from genmon directory if not os.path.isfile(self.configfile): self.LogInfo("Warning: unable to find config file: " + self.configfile + " Copying file to " + self.ConfigFilePath + " directory.") if not self.CopyConfFile(): sys.exit(2) self.config = MyConfig(filename=self.configfile, section="genmon", log=self.log) if not self.GetConfig(): self.CopyConfFile() self.LogInfo("Error validating config. Retrying..") self.config = MyConfig(filename=self.configfile, section="genmon", log=self.log) if not self.GetConfig(): self.LogInfo( "Error reading config file, 2nd attempt (1), Exiting") sys.exit(2) if not self.ValidateConfig(): self.CopyConfFile() self.LogInfo("Error validating config. Retrying..") self.config = MyConfig(filename=self.configfile, section="genmon", log=self.log) if not self.GetConfig(): self.LogInfo( "Error reading config file, 2nd attempt (2), Exiting") sys.exit(2) if not self.ValidateConfig(): self.LogInfo("Error validating config file, Exiting") sys.exit(2) self.LoadOrder = self.GetLoadOrder() if self.Stop: self.StopModules() time.sleep(2) if self.Start: self.StartModules() except Exception as e1: self.LogErrorLine("Error in init: " + str(e1)) #--------------------------------------------------------------------------- def CopyConfFile(self): if os.path.isfile(os.path.join(self.ConfPath, self.ConfigFileName)): copyfile(os.path.join(self.ConfPath, self.ConfigFileName), self.configfile) return True else: self.LogInfo("Unable to find config file.") return False sys.exit(2) #--------------------------------------------------------------------------- def CheckSystem(self): # this function checks the system to see if the required libraries are # installed. If they are not then an attempt is made to install them. ModuleList = [ # [import name , install name, required version] ['flask', 'flask', None], # Web server # we will not use the check for configparser as this look like it is in backports on 2.7 # and our myconfig modules uses the default so this generates an error that is not warranted #['configparser','configparser',None], # reading config files ['serial', 'pyserial', None], # Serial ['crcmod', 'crcmod', None], # Modbus CRC ['pyowm', 'pyowm', '2.10.0'], # Open Weather API ['pytz', 'pytz', None], # Time zone support ['pysnmp', 'pysnmp', None], # SNMP ['ldap3', 'ldap3', None], # LDAP ['smbus', 'smbus', None], # SMBus reading of temp sensors ['pyotp', 'pyotp', '2.3.0'], # 2FA support ['psutil', 'psutil', None], # process utilities ['chump', 'chump', None], # for genpushover ['twilio', 'twilio', None], # for gensms ['paho.mqtt.client', 'paho-mqtt', None], # for genmqtt ['OpenSSL', 'pyopenssl', None], # SSL ['spidev', 'spidev', None] # spidev ] try: ErrorOccured = False for Module in ModuleList: if not self.LibraryIsInstalled(Module[0]): self.LogInfo("Warning: required library " + Module[1] + " not installed. Attempting to install....") if not self.InstallLibrary(Module[1], version=Module[2]): self.LogInfo("Error: unable to install library " + Module[1]) ErrorOccured = True if Module[0] == "ldap3": # This will correct and issue with the ldap3 modbule not being recogonized in LibrayIsInstalled self.InstallLibrary("pyasn1", update=True) return not ErrorOccured except Exception as e1: self.LogInfo("Error in CheckSystem: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def CheckBaseSoftware(self): try: if self.PipChecked: return True if sys.version_info[0] < 3: pipProgram = "pip2" else: pipProgram = "pip3" install_list = [pipProgram, '-V'] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in CheckBaseSoftware : " + str(_error)) rc = process.returncode self.PipChecked = True return True except Exception as e1: self.LogInfo("Error in CheckBaseSoftware: " + str(e1), LogLine=True) self.InstallBaseSoftware() return False #--------------------------------------------------------------------------- def InstallBaseSoftware(self): try: if sys.version_info[0] < 3: pipProgram = "python-pip" else: pipProgram = "python3-pip" self.LogInfo("Installing " + pipProgram) install_list = ["sudo", "apt-get", "-yqq", "update"] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in InstallBaseSoftware : " + str(_error)) rc = process.returncode install_list = ["sudo", "apt-get", "-yqq", "install", pipProgram] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() return True except Exception as e1: self.LogInfo("Error in InstallBaseSoftware: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- @staticmethod def OneTimeMaint(ConfigFilePath, log): FileList = { "feedback.json": os.path.dirname(os.path.realpath(__file__)) + "/", "outage.txt": os.path.dirname(os.path.realpath(__file__)) + "/", "kwlog.txt": os.path.dirname(os.path.realpath(__file__)) + "/", "maintlog.json": os.path.dirname(os.path.realpath(__file__)) + "/", "Feedback_dat": os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/", "Message_dat": os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/", 'genmon.conf': "/etc/", 'genserv.conf': "/etc/", 'gengpio.conf': "/etc/", 'gengpioin.conf': "/etc/", 'genlog.conf': "/etc/", 'gensms.conf': "/etc/", 'gensms_modem.conf': "/etc/", 'genpushover.conf': "/etc/", 'gensyslog.conf': "/etc/", 'genmqtt.conf': "/etc/", 'genslack.conf': "/etc/", 'genexercise.conf': "/etc/", 'genemail2sms.conf': "/etc/", 'genloader.conf': "/etc/", 'mymail.conf': "/etc/", 'mymodem.conf': "/etc/" } try: # Check to see if we have done this already by checking files in the genmon source directory if (not os.path.isfile( os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/Message_dat") and not os.path.isfile( os.path.dirname(os.path.realpath(__file__)) + "/maintlog.json") and not os.path.isfile( os.path.dirname(os.path.realpath(__file__)) + "/outage.txt") and not os.path.isfile( os.path.dirname(os.path.realpath(__file__)) + "/kwlog.txt") and not os.path.isfile("/etc/genmon.conf")): return False # validate target directory if not os.path.isdir(ConfigFilePath): try: os.mkdir(ConfigFilePath) if not os.access(ConfigFilePath + File, os.R_OK): pass except Exception as e1: log.error("Error validating target directory: " + str(e1), LogLine=True) # move files for File, Path in FileList.items(): try: SourceFile = Path + File if os.path.isfile(SourceFile): log.error("Moving " + SourceFile + " to " + ConfigFilePath) if not MySupport.CopyFile(SourceFile, ConfigFilePath + File, move=True, log=log): log.error("Error: using alternate move method") move(SourceFile, ConfigFilePath + File) if not os.access(ConfigFilePath + File, os.R_OK): pass except Exception as e1: log.error("Error moving " + SourceFile) except Exception as e1: log.error("Error moving files: " + str(e1), LogLine=True) return True #--------------------------------------------------------------------------- def FixPyOWMMaintIssues(self): try: # check version of pyowm import pyowm if sys.version_info[0] < 3: required_version = "2.9.0" else: required_version = "2.10.0" if not self.LibraryIsInstalled("pyowm"): self.LogError( "Error in FixPyOWMMaintIssues: pyowm not installed") return False installed_version = self.GetLibararyVersion("pyowm") if installed_version == None: self.LogError( "Error in FixPyOWMMaintIssues: pyowm version not found") return None if self.VersionTuple(installed_version) <= self.VersionTuple( required_version): return True self.LogInfo( "Found wrong version of pyowm, uninstalling and installing the correct version." ) self.InstallLibrary("pyowm", uninstall=True) self.InstallLibrary("pyowm", version=required_version) return True except Exception as e1: self.LogErrorLine("Error in FixPyOWMMaintIssues: " + str(e1)) return False #--------------------------------------------------------------------------- def GetLibararyVersion(self, libraryname, importonly=False): try: try: import importlib my_module = importlib.import_module(libraryname) return my_module.__version__ except: if importonly: return None # if we get here then the libarary does not support a __version__ attribute # lets use pip to get the version try: if sys.version_info[0] < 3: pipProgram = "pip2" else: pipProgram = "pip3" # This will check if pip is installed if "linux" in sys.platform: self.CheckBaseSoftware() install_list = [pipProgram, 'freeze', libraryname] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo( "Error in GetLibararyVersion using pip : " + libraryname + ": " + str(_error)) rc = process.returncode # process output of pip freeze lines = output.splitlines() for line in lines: line = line.decode("utf-8") line = line.strip() if line.startswith(libraryname): items = line.split('==') if len(items) <= 2: return items[1] return None except Exception as e1: self.LogInfo("Error getting version of module: " + libraryname + ": " + str(e1), LogLine=True) return None except Exception as e1: self.LogErrorLine("Error in GetLibararyVersion: " + str(e1)) return None #--------------------------------------------------------------------------- def LibraryIsInstalled(self, libraryname): try: import importlib my_module = importlib.import_module(libraryname) return True except Exception as e1: return False #--------------------------------------------------------------------------- def InstallLibrary(self, libraryname, update=False, version=None, uninstall=False): try: if sys.version_info[0] < 3: pipProgram = "pip2" else: pipProgram = "pip3" if version != None and uninstall == False: libraryname = libraryname + "==" + version # This will check if pip is installed if "linux" in sys.platform: self.CheckBaseSoftware() if update: install_list = [pipProgram, 'install', libraryname, '-U'] elif uninstall: install_list = [pipProgram, 'uninstall', '-y', libraryname] else: install_list = [pipProgram, 'install', libraryname] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in InstallLibrary using pip : " + libraryname + " : UnInstall: " + str(uninstall) + ": " + str(_error)) rc = process.returncode return True except Exception as e1: self.LogInfo("Error installing module: " + libraryname + " : UnInstall: " + str(uninstall) + ": " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def ValidateConfig(self): ErrorOccured = False if not len(self.CachedConfig): self.LogInfo("Error: Empty configruation found.") return False for Module, Settiings in self.CachedConfig.items(): try: if self.CachedConfig[Module]["enable"]: if not os.path.isfile( os.path.join(self.ModulePath, self.CachedConfig[Module]["module"])): self.LogInfo( "Enable to find file " + os.path.join(self.ModulePath, self.CachedConfig[Module]["module"])) ErrorOccured = True # validate config file and if it is not there then copy it. if not self.CachedConfig[Module]["conffile"] == None and len( self.CachedConfig[Module]["conffile"]): ConfFileList = self.CachedConfig[Module]["conffile"].split( ",") for ConfigFile in ConfFileList: ConfigFile = ConfigFile.strip() if not os.path.isfile( os.path.join(self.ConfigFilePath, ConfigFile)): if os.path.isfile( os.path.join(self.ConfPath, ConfigFile)): self.LogInfo("Copying " + ConfigFile + " to " + self.ConfigFilePath) copyfile( os.path.join(self.ConfPath, ConfigFile), os.path.join(self.ConfigFilePath, ConfigFile)) else: self.LogInfo( "Enable to find config file " + os.path.join(self.ConfPath, ConfigFile)) ErrorOccured = True except Exception as e1: self.LogInfo("Error validating config for " + Module + " : " + str(e1), LogLine=True) return False try: if not self.CachedConfig["genmon"]["enable"]: self.LogError( "Warning: Genmon is not enabled, assume corrupt file.") ErrorOccured = True if not self.CachedConfig["genserv"]["enable"]: self.LogError("Warning: Genserv is not enabled") except Exception as e1: self.LogErrorLine( "Error in ValidateConfig, possible corrupt file. " + str(e1)) ErrorOccured = True return not ErrorOccured #--------------------------------------------------------------------------- def AddEntry(self, section=None, module=None, conffile="", args="", priority='2'): try: if section == None or module == None: return self.config.WriteSection(section) self.config.WriteValue('module', module, section=section) self.config.WriteValue('enable', 'False', section=section) self.config.WriteValue('hardstop', 'False', section=section) self.config.WriteValue('conffile', conffile, section=section) self.config.WriteValue('args', args, section=section) self.config.WriteValue('priority', priority, section=section) except Exception as e1: self.LogInfo("Error in AddEntry: " + str(e1), LogLine=True) return #--------------------------------------------------------------------------- def UpdateIfNeeded(self): try: self.config.SetSection("gengpioin") if not self.config.HasOption('conffile'): self.config.WriteValue('conffile', "gengpioin.conf", section="gengpioin") self.LogError("Updated entry gengpioin.conf") else: defValue = self.config.ReadValue('conffile', default="") if not len(defValue): self.config.WriteValue('conffile', "gengpioin.conf", section="gengpioin") self.LogError("Updated entry gengpioin.conf") self.config.SetSection("gengpio") if not self.config.HasOption('conffile'): self.config.WriteValue('conffile', "gengpio.conf", section="gengpio") self.LogError("Updated entry gengpio.conf") else: defValue = self.config.ReadValue('conffile', default="") if not len(defValue): self.config.WriteValue('conffile', "gengpio.conf", section="gengpio") self.LogError("Updated entry gengpio.conf") # check version info self.config.SetSection("genloader") self.version = self.config.ReadValue("version", default="0.0.0") if self.version == "0.0.0" or not len(self.version): self.version = "0.0.0" self.NewInstall = True if self.VersionTuple(self.version) < self.VersionTuple( ProgramDefaults.GENMON_VERSION): self.Upgrade = True if self.NewInstall or self.Upgrade: self.config.WriteValue("version", ProgramDefaults.GENMON_VERSION, section='genloader') if self.NewInstall: self.LogInfo("Running one time maintenance check") self.FixPyOWMMaintIssues() # TODO other version checks can be added here self.version = ProgramDefaults.GENMON_VERSION except Exception as e1: self.LogInfo("Error in UpdateIfNeeded: " + str(e1), LogLine=True) #--------------------------------------------------------------------------- def GetConfig(self): try: Sections = self.config.GetSections() ValidSections = [ 'genmon', 'genserv', 'gengpio', 'gengpioin', 'genlog', 'gensms', 'gensms_modem', 'genpushover', 'gensyslog', 'genmqtt', 'genslack', 'genexercise', 'genemail2sms', 'gentankutil', 'gentankdiy', 'genalexa', 'gensnmp', 'gentemp', 'gengpioledblink', 'gencthat', 'genloader' ] for entry in ValidSections: if not entry in Sections: if entry == 'genmon' or entry == 'genserv': self.LogError("Warning: Missing entry: " + entry + " , file corruption. ") return False if entry == 'genslack': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genslack.py', conffile='genslack.conf') if entry == 'genexercise': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genexercise.py', conffile='genexercise.conf') if entry == 'genemail2sms': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genemail2sms.py', conffile='genemail2sms.conf') if entry == 'gentankutil': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gentankutil.py', conffile='gentankutil.conf') if entry == 'genalexa': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genalexa.py', conffile='genalexa.conf') if entry == 'gensnmp': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gensnmp.py', conffile='gensnmp.conf') if entry == 'gentemp': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gentemp.py', conffile='gentemp.conf') if entry == 'gentankdiy': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gentankdiy.py', conffile='gentankdiy.conf') if entry == 'gengpioledblink': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gengpioledblink.py', conffile='gengpioledblink.conf') if entry == 'gencthat': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gencthat.py', conffile='gencthat.conf') if entry == 'genloader': self.LogError("Adding entry: " + entry) self.config.WriteSection(entry) else: self.LogError("Warning: Missing entry: " + entry) self.UpdateIfNeeded() Sections = self.config.GetSections() for SectionName in Sections: if SectionName == 'genloader': continue TempDict = {} self.config.SetSection(SectionName) if self.config.HasOption('module'): TempDict['module'] = self.config.ReadValue('module') else: self.LogError( "Error in GetConfig: expcting module in section " + str(SectionName)) TempDict['module'] = None if self.config.HasOption('enable'): TempDict['enable'] = self.config.ReadValue( 'enable', return_type=bool) else: self.LogError( "Error in GetConfig: expcting enable in section " + str(SectionName)) TempDict['enable'] = False if self.config.HasOption('hardstop'): TempDict['hardstop'] = self.config.ReadValue( 'hardstop', return_type=bool) else: self.LogError( "Error in GetConfig: expcting hardstop in section " + str(SectionName)) TempDict['hardstop'] = False if self.config.HasOption('conffile'): TempDict['conffile'] = self.config.ReadValue('conffile') else: self.LogError( "Error in GetConfig: expcting confile in section " + str(SectionName)) TempDict['conffile'] = None if self.config.HasOption('args'): TempDict['args'] = self.config.ReadValue('args') else: self.LogError( "Error in GetConfig: expcting args in section " + str(SectionName)) TempDict['args'] = None if self.config.HasOption('priority'): TempDict['priority'] = self.config.ReadValue( 'priority', return_type=int, default=None) else: self.LogError( "Error in GetConfig: expcting priority in section " + str(SectionName)) TempDict['priority'] = None if self.config.HasOption('postloaddelay'): TempDict['postloaddelay'] = self.config.ReadValue( 'postloaddelay', return_type=int, default=0) else: TempDict['postloaddelay'] = 0 if self.config.HasOption('pid'): TempDict['pid'] = self.config.ReadValue('pid', return_type=int, default=0, NoLog=True) else: TempDict['pid'] = 0 self.CachedConfig[SectionName] = TempDict return True except Exception as e1: self.LogInfo("Error parsing config file: " + str(e1), LogLine=True) return False return True #--------------------------------------------------------------------------- def ConvertToInt(self, value, default=None): try: return int(str(value)) except: return default #--------------------------------------------------------------------------- def GetLoadOrder(self): LoadOrder = [] LoadDict = {} try: for Module, Settiings in self.CachedConfig.items(): # get the load order of all modules, even if they are disabled # since we need to stop all modules (even disabled ones) if the # conf file changed try: if self.CachedConfig[Module]["priority"] == None: LoadDict[Module] = 99 elif self.CachedConfig[Module]["priority"] >= 0: LoadDict[Module] = self.CachedConfig[Module][ "priority"] else: LoadDict[Module] = 99 except Exception as e1: self.LogInfo("Error reading load order (retrying): " + str(e1), LogLine=True) #lambda kv: (-kv[1], kv[0]) for key, value in sorted(LoadDict.items(), key=lambda kv: (-kv[1], kv[0])): #for key, value in sorted(LoadDict.iteritems(), key=lambda (k,v): (v,k)): LoadOrder.append(key) except Exception as e1: self.LogInfo("Error reading load order: " + str(e1), LogLine=True) return LoadOrder #--------------------------------------------------------------------------- def StopModules(self): self.LogConsole("Stopping....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to stop.") return False ErrorOccured = False for Module in self.LoadOrder: try: if not self.UnloadModule( self.ModulePath, self.CachedConfig[Module]["module"], pid=self.CachedConfig[Module]["pid"], HardStop=self.CachedConfig[Module]["hardstop"], UsePID=True): self.LogInfo("Error stopping " + Module) ErrorOccured = True except Exception as e1: self.LogInfo("Error stopping module " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def StartModules(self): self.LogConsole("Starting....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to start.") return False ErrorOccured = False for Module in reversed(self.LoadOrder): try: if self.CachedConfig[Module]["enable"]: if not multi_instance: # check that module is not loaded already, if it is then force it (hard) to unload attempts = 0 while True: if MySupport.IsRunning( prog_name=self.CachedConfig[Module] ["module"], log=self.log, multi_instance=multi_instance): # if loaded then kill it if attempts >= 4: # kill it if not self.UnloadModule( self.ModulePath, self.CachedConfig[Module] ["module"], pid=None, HardStop=True, UsePID=False): self.LogInfo("Error killing " + self.CachedConfig[Module] ["module"]) else: attempts += 1 time.sleep(1) else: break if not self.LoadModule( self.ModulePath, self.CachedConfig[Module]["module"], args=self.CachedConfig[Module]["args"]): self.LogInfo("Error starting " + Module) ErrorOccured = True if not self.CachedConfig[Module][ "postloaddelay"] == None and self.CachedConfig[ Module]["postloaddelay"] > 0: time.sleep(self.CachedConfig[Module]["postloaddelay"]) except Exception as e1: self.LogInfo("Error starting module " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def LoadModuleAlt(self, modulename, args=None): try: self.LogConsole("Starting " + modulename) # to load as a background process we just use os.system since Popen # is problematic in doing this CommandString = sys.executable + " " + modulename if args != None and len(args): CommandString += " " + args CommandString += " &" os.system(CommandString) return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def LoadModule(self, path, modulename, args=None): try: try: import os fullmodulename = os.path.join(path, modulename) except Exception as e1: fullmodulename = path + "/" + modulename if args != None: self.LogConsole("Starting " + fullmodulename + " " + args) else: self.LogConsole("Starting " + fullmodulename) try: from subprocess import DEVNULL # py3k except ImportError: import os DEVNULL = open(os.devnull, 'wb') if not len(args): args = None if "genserv.py" in modulename: OutputStream = DEVNULL else: OutputStream = subprocess.PIPE executelist = [sys.executable, fullmodulename] if args != None: executelist.extend(args.split(" ")) # This will make all the programs use the same config files executelist.extend(["-c", self.ConfigFilePath]) # close_fds=True pid = subprocess.Popen(executelist, stdout=OutputStream, stderr=OutputStream, stdin=OutputStream) return self.UpdatePID(modulename, pid.pid) except Exception as e1: self.LogInfo("Error loading module " + path + ": " + modulename + ": " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def UnloadModule(self, path, modulename, pid=None, HardStop=False, UsePID=False): try: LoadInfo = [] if UsePID: if pid == None or pid == "" or pid == 0: return True LoadInfo.append("kill") if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append(str(pid)) else: LoadInfo.append('pkill') if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append('-u') LoadInfo.append('root') LoadInfo.append('-f') LoadInfo.append(modulename) self.LogConsole("Stopping " + modulename) process = Popen(LoadInfo, stdout=PIPE) output, _error = process.communicate() rc = process.returncode return self.UpdatePID(modulename, "") except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def UpdatePID(self, modulename, pid=None): try: filename = os.path.splitext(modulename)[0] # remove extension if not self.config.SetSection(filename): self.LogError("Error settting section name in UpdatePID: " + str(filename)) return False self.config.WriteValue("pid", str(pid)) return True except Exception as e1: self.LogInfo("Error writing PID for " + modulename + " : " + str(e1)) return False return True