import repository from arizona
[raven.git] / apps / servicemonitor / servicemonitor.py
1 #! /usr/bin/env python
2
3 import atexit
4 import ConfigParser
5 import fcntl
6 import fnmatch
7 import logging
8 from logging.handlers import RotatingFileHandler
9 import os
10 import subprocess
11 import sys
12 import time
13
14 LOG_FILENAME="/var/log/servicemonitor"
15 CONF_DIR="/etc/servicemonitor.d"
16
17 # this global will be filled in by main
18 logger = None
19
20 # I want servicemonitor to stand alone, so I stole this from arizonageneral
21 def mutex_lock(program, lockdir="/var/lock", unlock_on_exit=True):
22    try:
23       os.makedirs(lockdir)
24    except OSError:
25       pass
26
27    lock = file(lockdir + "/" + program + ".lock", "w")
28
29    try:
30       fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
31    except IOError:
32       lock.close()
33       return None
34
35    if unlock_on_exit:
36       atexit.register(mutex_unlock, lock)
37    return lock
38
39 def mutex_unlock(lock):
40    if lock != None:
41       if not lock.closed:
42          filename = lock.name
43          fcntl.flock(lock, fcntl.LOCK_UN)
44          lock.close()
45          if os.path.isfile(filename):
46             os.remove(filename)
47
48 # stolen from Owl
49 class MyParser(ConfigParser.SafeConfigParser):
50     def GetOpt(self, section, option, default):
51         try:
52             value = self.get(section, option)
53         except ConfigParser.NoOptionError:
54             value = default
55         except ConfigParser.NoSectionError:
56             print "Section '%s' missing from config file '%s'" % (section,self.path)
57             exit(1)
58         return value
59
60     def Get(self, section, option):
61         try:
62             value = self.get(section, option)
63         except ConfigParser.NoOptionError:
64             print "No option '%s' in section '%s' in  file '%s'" % (option,section,self.path)
65             exit(1)
66         except ConfigParser.NoSectionError:
67             print "No section '%s' in config file '%s'" % (section,self.path)
68             exit(1)
69         return value
70
71     def Read(self, path, bail):
72         self.path = path
73         f = []
74         try:
75             f = self.read(path)
76         except ConfigParser.ParsingError :
77             print "Unable to parse '%s'" % (self.path)
78             if (bail):
79                 exit(1)
80         return f
81
82 def process_exists(processname):
83     p = subprocess.Popen(["/bin/ps", "ax", "-o", "pid,command"], stdout=subprocess.PIPE)
84     p.wait()
85     lines = p.stdout.readlines()
86     for line in lines:
87         line = line.strip()
88         (pid, command) = line.split(" ", 1)
89         if fnmatch.fnmatch(command, processname + "*"):
90             return True
91     return False
92
93 def restart(initscript):
94     devnull = file("/dev/null", "w")
95     logging.info("executing: " + initscript)
96     subprocess.Popen(initscript + " restart &", shell=True, stdout=devnull, stderr=devnull)
97
98 def process_config_file(pathname):
99     print "processing config file", pathname
100
101     cfp = MyParser()
102     cfp.Read(pathname, True)
103
104     for section in cfp.sections():
105         initscript = cfp.GetOpt(section, "initscript", "")
106         processname = cfp.GetOpt(section, "processname", "")
107
108         if not initscript:
109             logging.warning("[" + section + "] initscript is null")
110             continue
111
112         if not os.path.exists(initscript):
113             logging.warning("[" + section + "] initscript " + initscript + " does not exist")
114             continue
115
116         if not processname:
117             logging.warning("[" + section + "] processname is null")
118             continue
119
120         if not process_exists(processname):
121             logging.warning("[" + section + "] process is not running. restarting.")
122             restart(initscript)
123         else:
124             logging.debug("[" + section + "] process is running.")
125
126 def process_config_dir(dir):
127     print "checking for config files in", dir
128
129     if not os.path.exists(dir):
130         logging.critical("directory " + dir + " does not exist")
131         return
132
133     if not os.path.isdir(dir):
134         logging.critical("directory " + dir + " is not a directory")
135         return
136
137     files = os.listdir(dir)
138     files.sort()
139
140     for filename in files:
141         if filename.startswith("."):
142             continue
143         if filename.endswith("~"):
144             continue
145         pathname = os.path.join(CONF_DIR, filename)
146         if os.path.isdir(pathname):
147             continue
148         process_config_file(pathname)
149
150 def main():
151     if os.geteuid() > 0:
152         print >> sys.stderr, "You must be root to run this program..."
153         sys.exit(-1)
154
155     # create a fileHandler, so we can send output to both stderr and a rotating file
156     fileHandler = RotatingFileHandler(LOG_FILENAME, maxBytes=1*1024*1024, backupCount=1)
157     fileHandler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
158     fileHandler.setLevel(logging.INFO)
159
160     logging.basicConfig(stream=sys.stderr, level = logging.DEBUG)
161     logging.getLogger('').addHandler(fileHandler)
162
163     # mutex to prevent multiple copies
164     if not mutex_lock("servicemonitor"):
165         logging.error("servicemonitor is already running. aborting.")
166         sys.exit(-1)
167
168     process_config_dir(CONF_DIR)
169
170 if __name__=="__main__":
171    main()