def eddielog(self, *arg): # if one argument supplied, this text is logged with a loglevel of 0 # if two arguments, first is text to log, second is log level. if len(arg) < 0 or len(arg) > 2: log.log( "<action>action.eddielog(): Error, %d arguments found, expecting 1 or 2" % (len(arg)), 5 ) return logstr = arg[0] # if two arguments given, second arg is log level. Otherwise assume 0. if len(arg) == 2: loglevel = string.atoi(arg[1]) # If loglevel out of range, error. if loglevel < log.loglevel_min or loglevel > log.loglevel_max: log.log( "<action>action.eddielog(): Error, loglevel out of range, loglevel is %d, range is %d-%d" % (loglevel,log.loglevel_min,log.loglevel_max), 5 ) return else: loglevel = log.loglevel_min # Parse the text string to replace variables logstr = utils.parseVars( logstr, self.varDict ) # Log the text retval = log.log( logstr, loglevel ) # Alert if return value == 0 if retval == 0: log.log( "<action>action.eddielog(): Alert, return value for log.log( '%s', %d ) is %d" % (logstr,loglevel,retval), 5 ) else: log.log( "<action>action.eddielog(): text '%s', loglevel %d" % (logstr,loglevel), 6 )
def system(self, cmd): """This action allows execution of external commands via the os.system() call. The only user argument, 'cmd', contains the string of commands to execute. TODO: can/should we check this cmd for security problems ?? """ # Substitute variables in string cmd = utils.parseVars( cmd, self.varDict ) if len(cmd) == 0: log.log( "<action>action.system(): Error, no command given", 5 ) return # Call system() to execute the command log.log( "<action>action.system(): calling os.system() with cmd '%s'" % (cmd), 6 ) # TODO: possibly not thread-safe to simply call os.system() without # locking; this might have to be part of the thread-safe group of # system commands in utils.py retval = os.system( cmd ) # Alert if return value != 0 if retval != 0: log.log( "<action>action.system(): Alert, return value for cmd '%s' is %d" % (cmd,retval), 5 ) log.log( "<action>action.system(): cmd '%s', return value %d" % (cmd,retval), 6 )
def restart(self, cmd): # cmd is cmd to be executed with: '/etc/init.d/cmd start'. # cmd should not contain any path information, hence if '/'s are found it # is not executed. # Substitute variables in string cmd = utils.parseVars( cmd, self.varDict ) # Security: if cmd contains any illegal characters, "/#;!$%&*|~`", then we abort. #if string.find( cmd, '/' ) != -1: if utils.charpresent( cmd, '/#;!$%&*|~`' ) != 0: log.log( "<action>action.restart(): Alert, restart() arg contains illegal character and is not being executed, cmd is '%s'" % (cmd), 5 ) return 1001 if len(cmd) == 0: log.log( "<action>action.restart(): Error, no command given", 5 ) return 1002 # Build command cmd = '/etc/init.d/'+cmd+' start' # Call system() to execute the command log.log( "<action>action.restart(): calling os.system() with cmd '%s'" % (cmd), 6 ) # TODO: possibly not thread-safe to simply call os.system() without # locking; this might have to be part of the thread-safe group of # system commands in utils.py retval = os.system( cmd ) # Alert if return value != 0 if retval != 0: log.log( "<action>action.restart(): Alert, return value for cmd '%s' is %d" % (cmd,retval), 5 ) log.log( "<action>action.restart(): cmd '%s', return value %d" % (cmd,retval), 6 ) return retval
def netsaint(self, desc, output, retcode): """ Send information to remote netsaint server via Elvin. Usage: action=netsaint("EddieFS", "Disk / at %(fscapac)s percent", 1) By Dougal Scott <*****@*****.**> """ log.log("<action>action.netsaint(desc='%s', output='%s', retcode=%s)" % (desc,output,retcode), 8) if eddieElvin4.UseElvin == 0: log.log("<action>action.netsaint(), Elvin is not available - skipping.", 8) return 0 datadict = {} datadict['hostname']=log.hostname datadict['desc']=desc datadict['output']=utils.parseVars(output, self.varDict) datadict['return']=retcode retval = elvin.netsaint(datadict) # Alert if return value != 0 if retval != 0: log.log("<action>action.netsaint(%s), Alert, return value for e.send() is %d" % (datadict,retval), 3) else: log.log("<action>action.netsaint(%s): store ok" % datadict, 7)
def elvinrrd(self, key, *data): """Send information to remote RRDtool database listener via Elvin. key is the RRD name configured in the Elvin consumer to store the data. data is a variable length list of strings of the form 'var=data' where var is the RRD variable name and data is the data to store. Example use in a directive: SYS loadavg1_rrd: rule='1' # always true scanperiod='1m' action="elvinrrd('loadavg1-%(h)s', 'loadavg1=%(sysloadavg1)f')" """ log.log( "<action>action.elvinrrd( key='%s' data='%s' )"%(key,data), 6 ) sendrrd = None if eddieSpread.UseSpread: sendrrd = spread.rrd elif eddieElvin4.UseElvin: sendrrd = elvin.elvinrrd if not sendrrd: log.log( "<action>action.elvinrrd(): No messaging system available - skipping.", 5 ) return 0 if len(data) == 0: raise "elvinrrd exception, no data supplied" datadict = {} for d in data: (var,val) = string.split(d, '=') if var == None or var == '' or val == None: raise "elvinrrd exception, data error: var='%s' val='%s'" % (var,val) var = utils.parseVars( var, self.varDict ) # substitute variables val = utils.parseVars( val, self.varDict ) # substitute variables datadict[var] = val key = utils.parseVars( key, self.varDict ) # substitute variables log.log( "<action>action.elvinrrd() sending: key='%s' data=%s"%(key,datadict), 6 ) retval = sendrrd(key, datadict) # Alert if return value != 0 if retval != 0: log.log( "<action>action.elvinrrd(%s, %s), Alert, return value for e.send() is %d" % (key,datadict,retval), 5 ) else: log.log( "<action>action.elvinrrd(%s, %s): completed" % (key,datadict), 6 )
def elvindb(self, table, data=None): """Send information to remote database listener via Elvin. Data to insert in db can be specified in the data argument as 'col1=data1, col2=data2, col3=data3' or if data is None it will use self.storedict """ log.log( "<action>action.elvindb( table='%s' data='%s' )"%(table,data), 6 ) if eddieElvin4.UseElvin == 0: log.log( "<action>action.elvindb(): Elvin is not available - skipping.", 5 ) return 0 if data == None: # if nothing passed in data storedict will be used #retval = e.send(table, self.storedict) retval = elvin.elvindb(table, self.storedict) else: data = utils.parseVars( data, self.varDict ) # substitute variables datas = string.split(data, ',') # separate key/val pairs storedict = {} for d in datas: (key,val) = string.split(d, '=') # separate key & value key = string.strip(key) # strip spaces val = utils.stripquote(val) # strip spaces then quotes # try to convert val to float or int if possible try: val = float(val) except ValueError: try: val = int(val) except ValueError: pass storedict[key] = val retval = elvin.elvindb(table, storedict) # Alert if return value != 0 if retval != 0: log.log( "<action>action.elvindb('%s', dict): Alert, return value for e.send() is %d" % (table,retval), 5 ) else: log.log( "<action>action.elvindb('%s', dict): completed" % (table), 6 )
def ticker(self, msg, timeout=10): """A directive action to send a standard Elvin ticker message. See http://elvin.dstc.edu.au/projects/tickertape/ for clients and more info. - 'msg' is the text string to be send (TICKERTEXT) - 'timeout' is the timeout setting for the ticker message (minutes) """ if eddieElvin4.UseElvin == 0: log.log( "<action>action.ticker(): Elvin is not available - skipping.", 5 ) return 0 if type(msg) != type("string"): # if msg is not a string, assume it is a MSG object body = msg.subject else: # msg is a string body = msg if len(body) == 0: # body must contain something log.log( "<action>action.ticker(): Error, body is empty", 5 ) return if type(timeout) != type(1): # timeout must be integer log.log( "<action>action.ticker(): Error, timeout is not integer", 5 ) return # Substitute variables in string body = utils.parseVars( body, self.varDict ) retval = elvin.Ticker( body, timeout ) # Alert if return value != 0 if retval != 0: log.log( "<action>action.ticker(): Alert, return value for e.sendmsg('%s') is %d" % (body,retval), 5 ) else: log.log( "<action>action.ticker('%s') complete" % (body), 6 )
def email(self, address, subject="", body=""): """The standard email action. address should be a standard string containing a standard email address or list of email addresses separated by ','. subject should be either a standard string which will be used as the email subject the name of a MSG object. body should be a string containing the body of the email. """ if type(subject) != type("string"): # if subject is not a string, assume it is a MSG object body = subject.message subj = subject.subject else: subj = subject # replace text that look like newlines with newlines body = body.replace('\\n', '\n') # Create problem age and other statistics if this is not the first time # the problem was found. # Stored in %(problemage)s and %(problemfirstdetect)s self.varDict['problemage'] = '' self.varDict['problemfirstdetect'] = '' t = self.state.faildetecttime tl = self.state.lastfailtime if tl != t: tage = self.state.age() agestr = "Problem age: " if tage[0] > 0: agestr = agestr + " %d year" % tage[0] if tage[0] > 1: agestr = agestr + "s" if tage[1] > 0: agestr = agestr + " %d month" % tage[1] if tage[1] > 1: agestr = agestr + "s" if tage[2] > 0: agestr = agestr + " %d day" % tage[2] if tage[2] > 1: agestr = agestr + "s" if tage[3] > 0: agestr = agestr + " %d hour" % tage[3] if tage[3] > 1: agestr = agestr + "s" if tage[4] > 0: agestr = agestr + " %d minute" % tage[4] if tage[4] > 1: agestr = agestr + "s" if tage[5] > 0: agestr = agestr + " %d second" % tage[5] if tage[5] > 1: agestr = agestr + "s" if agestr != "": self.varDict['problemage'] = agestr self.varDict['problemfirstdetect'] = "First detected: %04d/%02d/%02d %d:%02d:%02d" % (t[0], t[1], t[2], t[3], t[4], t[5]) # run thru utils.parseVars() to substitute variables from varDict address = utils.parseVars( address, self.varDict ) subj = utils.parseVars( subj, self.varDict ) body = utils.parseVars( body, self.varDict ) #headers = 'To: ' + address + '\n' + 'Subject: [' + log.hostname + '] ' + subj + '\n' headers = 'To: %s\nSubject: [%s] %s\n' % (address,log.hostname,subj) r = utils.sendmail( headers, body ) if r: log.log( "<action>action.email(): address='%s' subject='%s' body='%s...' successful)" % (address,subj,body[:20]), 6 ) else: log.log( "<action>action.email(): address='%s' subject='%s' body='%s...' failed)" % (address,subj,body[:20]), 4 )
def log(self, message, vias): """ Post messages to log file, eddie tty, or syslog. For logging to the eddie tty: 'via' should be 'tty' For logging by appending to a file: 'via' should begin with a '/' For logging via syslog: 'via' should be 'FACILITY.LEVEL' Note that you may specify multiple vias by separating them with a ';'. """ if type(message) != type("string"): # if msg is not a string, assume it is a MSG object message = message.subject # Substitute variables in message string message = utils.parseVars( message, self.varDict ) for via in vias.split(';'): # remove leading and trailing whitespace via = via.strip() # Determine if this is a syslog-type log request syslogmatch = re.match('^([A-Z0-9]+)\.([A-Z]+)$', via.upper()) # eddie tty logging (via == "tty") if via == 'tty': # get the current day/time datetime = time.asctime(time.localtime(time.time())) print "%s %s" % (datetime,message) # file logging (via like "/path/to/logfile") elif via.startswith('/'): try: f = open(via, 'a') # get the current day/time datetime = time.asctime(time.localtime(time.time())) f.write("%s %s\n" % (datetime,message)) f.close() except: log.log( "<action>action.log(): unable to open file for append '%s'" % (via), 5) # syslog logging (via like "USER.INFO") elif syslogmatch != None: """Post messages via syslog(). The parameter priority should be a string in "facility.level" format, defaulting to USER.INFO. See man syslog(3) for a list of valid facilities and levels, or the syslog.h header file. Feel free to use upper or lower case. Note that in order to see the log messages, your local syslog daemon must be configured to do something with the messages: see man syslog.conf(5) for details. """ global UseSyslog if not UseSyslog: #log.log( "<action>action.syslog(): syslog not supported on this system", 5) return # The priority is the syslog.LOG_(facility) | syslog.LOG_(level) try: prio = eval('syslog.LOG_%s' % (syslogmatch.group(1))) | eval('syslog.LOG_%s' % (syslogmatch.group(2))) except: log.log( "<action>action.log(): unable to parse valid syslog facility and level from '%s'" % (via), 5) return # Send the message off... note that syslog() is UDP, and therefore not # acknowledged, so we have no return value syslog.syslog(prio, message) else: log.log( "<action>action.log(): unknown logging via '%s'" % (via), 5) return
def getData(self): """ Grab data about the database """ data = {} data['connected'] = 0 data['errmsg'] = '' data['querytime'] = -1 if self.available == -1: try: exec("import "+self.args.dbtype+"db") self.dbi = eval(self.args.dbtype+"db") self.available = 1 except ImportError: # If it fails don't barf just nod and keep on going # It just won't be able to do anything self.available = 0 exctype,value = sys.exc_info()[:2] log.log("<dbi>getData(): %d db module is not importable %s, %s" % (self.args.dbtype,exctype,value), 5) if not self.available: data['errmsg'] = "dbi for %s is not available" % (self.args.dbtype) return data # Connect to the database # XXX(mtaylor): need a connection timeout here... try: try: db = self.dbi.connect(host=self.args.host, port=self.args.port, user=self.args.user, passwd=self.args.password) except TypeError: try: db = self.dbi.connect(host=self.args.host, user=self.args.user, password=self.args.password, database=self.args.database) except: data['errmsg'] = 'db connect failure' return data except: data['errmsg'] = 'db connect failure' return data curs = db.cursor() data['connected'] = 1 starttime = time.time() # allow for query like "SELECT COUNT(1) FROM x WHERE y > %(_maxval)d" q = utils.parseVars(self.args.query, self.defaultVarDict) # XXX(mtaylor): need a query timeout here... curs.execute(q) data['querytime'] = (time.time()-starttime)*1000 result = [] data['result1'] = '' # populate 'result1' so rule/console string may work instead of causing exception... ans = curs.fetchone() if curs.rowcount > 1: # Return results as 'result1', 'result2', ..., with *first column* of every row returns: count = 0 while ans != None: data['result%d'%(count+1)] = utils.typeFromString(ans[0]) count = count+1 result.append(ans) ans = curs.fetchone() data['results'] = count else: # Return results as 'result1', 'result2', ..., with *first row's* returns: for i in range(len(ans)): data['result%d'%(i+1)] = utils.typeFromString(ans[i]) result.append(ans) data['results'] = len(ans) data['result'] = result curs.close() db.close() # XXX(mtaylor): should create self.args.persist to persist the connection, and auto-reopen if it db gets restarted except self.dbi.OperationalError,msg: log.log("<dbi>DBI.getData(): ID '%s' Operational Error: %s" % (self.ID, msg), 7) data['errmsg'] = msg try: db.close() except: pass return data