import repository from arizona
[raven.git] / 2.0 / plush / pacmand.plush
1 #! /usr/bin/env python
2
3 # John H. Hartman
4 # Parts copied from storkupdated
5 #
6 # pacmand -- pacman daemon
7 # Runs pacman periodically
8 #
9 #           [option, long option,                     variable,                      action,       data,     default,                       metavar,                       description]
10 """arizonaconfig
11     options=[
12             ["-C",   "--configfile",  "configfile",  "store",       "string", "/usr/local/stork/etc/stork.conf", "FILE",      "use a different config file (/usr/local/stork/etc/stork.conf is the default)"],
13              ["",    "--pacman",                      "pacman",                      "store",      "string", "/usr/local/stork/bin/pacman", None,                          "command to run"],
14              ["",    "--pacmandsync",                 "pacmandsync",                 "store_true", None,     False,                         None,                          "run synchronously (don't detach)"],
15              ["",    "--pollmetadatafile",          "pollfile",                    "store",       "string", "/usr/local/stork/var/packageinfo/stork-repository.cs.arizona.edu/packageinfo/metafile",        "pollfile",                         "poll the filesystem for a new metadata file to indicate a pacman update."],
16              ["",    "--heartbeat",    "heartbeat",    "store_false",   None,    False,    None,   "invoke pacman every 30 seconds regardless of whether or not the metadata file has changed"],
17              ["",    "--timer",                       "pactimer",                    "store",      "int",    300,                           "pactimer",                    "Set time between pacman calls"],
18              ["", "--pacmanverbose", "pacmanverbose", "store_false", None, False, None, "Make pacman verbose"]
19         ]
20
21      #include the options from pacman
22     includes=["$MAIN/pacman"]
23 """
24
25 import sys,os,signal,time
26 #should be in the same directory as the other scripts when used...
27 #sys.path += ["../python/refactor"]
28 sys.path += ["/usr/local/stork/bin"]
29 import arizonaconfig, arizonareport, arizonacomm
30 import arizonageneral
31 import storkpseprdaemon
32 import threading
33 import hashlib
34
35
36 pollfilename = "/usr/local/stork/var/packageinfo/stork-repository.cs.arizona.edu/packageinfo/metafile"
37 verbose = 0
38 glo_cmd="echo command missing"
39
40 # poll the FS for a new metafile every few seconds,
41 # and invoke pacman if there has been a change
42 class FilePoller(threading.Thread):
43    """
44    <Purpose>
45       Initialize the thread to begin polling the FS for a new metadata file
46    """
47    def __init__(self, metadata_filename, verboseness):
48       threading.Thread.__init__(self)
49       self.metadata_filename = metadata_filename
50       self.timeout = time.time() + 5     # poll every 5 seconds
51       self.file_hash = ""        # file hash
52       self.verboseness = verboseness
53       if verboseness != 0:
54          print "[pacmand.plush]: Starting filepoller; will look at " + metadata_filename
55       self.start()
56
57    """
58    <Purpose>
59       Get the SHA1 hash of the metadata file
60    """
61    def get_hash(self, filename ):
62       sha1_hasher = hashlib.sha1()
63
64       metadata_file = open( filename, "r" )
65       for file_line in metadata_file.readlines(): 
66          sha1_hasher.update( file_line )
67
68       metadata_file.close()
69       # calculate the hash and return
70       return sha1_hasher.hexdigest()
71
72    """
73    <Purpose>
74       Poll the FS for the file matching self.metadata_filename
75       If the file exists AND has a different timestamp than
76       the currently-recognized file, then invoke pacman on it.
77    """
78    def run(self):
79       while True:
80           # are we expired?
81           if self.timeout < time.time():
82              # new timeout...
83              self.timeout = time.time() + 5
84          
85              if self.verboseness != 0:
86                 print "[pacmand.plush] polling for " + self.metadata_filename
87
88              # does the file exist?  Bail if not...
89              if os.path.exists( self.metadata_filename ) == False:
90                 if self.verboseness != 0:
91                    print "[pacmand.plush] file " + self.metadata_filename + " does not exist!"
92                 continue
93
94              # check the hash
95              sha1_hash = self.get_hash( self.metadata_filename )
96
97              # hashes different?
98              if self.file_hash != sha1_hash:
99
100                 if self.verboseness != 0:
101                     print "[pacmand.plush] file changed; invoking pacman"
102             
103                 # signal an update
104                 pacman_signal_event()
105                         
106                 pacman_update()
107
108                 pacman_clear_event()
109
110                 # pacman will have changed the file, so re-get the hash
111                 self.file_hash = self.get_hash( self.metadata_filename )
112                 
113              # end
114           else:
115             # sleep some to save CPU
116             time.sleep(1)
117          # end
118       # end
119    # end
120 # end
121
122
123 def check_initscript_running():
124     """
125     <Purpose>
126        Return True if the stork initscript is running, false otherwise.
127     """
128     if not os.path.exists("/var/run/stork_initscript.pid"):
129         return False
130
131     try:
132         file = open("/var/run/stork_initscript.pid")
133     except:
134         arizonareport.send_syslog(arizonareport.INFO, "failed to open the stork_initscript pid file")
135         return False
136
137     try:
138         initscript_pid = file.readline().strip()
139     except:
140         file.close()
141         arizonareport.send_syslog(arizonareport.INFO, "failed to get the pid from the stork_initscript pid file")
142         return False
143
144     file.close()
145
146     # "ps -p <pid>" should return 0 if the process exists, or nonzero if it
147     # does not exist
148
149     try:
150         result = os.system("ps -p " + initscript_pid + " > /dev/null")
151     except:
152         arizonareport.send_syslog(arizonareport.INFO, "exception while running ps to check stork_initscript pid")
153         return False
154
155     if result == 0:
156         return True
157     else:
158         return False
159
160 def wait_if_initscript_running():
161     """
162     <Purpose>
163        Wait for the stork initscript to finish running.
164     """
165     sent = False
166
167     while check_initscript_running():
168         if not sent:
169             arizonareport.send_syslog(arizonareport.INFO, "stork initscript is running. delaying in 30-second increments")
170             sent = True
171         time.sleep(30)
172
173     if sent:
174         arizonareport.send_syslog(arizonareport.INFO, "stork initscript is no longer running")
175
176 def handler_sighup(signum, frame):
177     """
178     <Purpose>
179        Intercepts the "hangup" signal, but doesn't do anything.
180        Simply causes the sleep to return.
181     """
182     pass
183
184 def pacman_signal_event():
185    global pacman_update_event
186
187    pacman_update_event.set()
188
189 def pacman_clear_event():
190     global pacman_update_event
191     pacman_update_event.clear()
192
193 def pacman_update():
194     global glo_cmd, verbose
195
196     if verbose:
197         arizonareport.send_syslog(arizonareport.INFO, "[pacmand] Executing: " + glo_cmd)
198     if sync:
199         print "[pacmand] executing: " + glo_cmd
200
201     rc = os.system(glo_cmd)
202
203     if rc == 0:
204         if verbose:
205             arizonareport.send_syslog(arizonareport.INFO, "[pacmand] command returned with status " + str(rc))
206     else:
207         arizonareport.send_syslog(arizonareport.ERROR, "[pacmand] Error executing '%s'" % glo_cmd)
208
209 def Main():
210     global glo_cmd
211     global sync
212     global verbose
213     global pacman_update_event
214     global filepoller
215     global pollfilename
216
217     args = arizonaconfig.init_options("pacmand.plush",version="2.0", configfile_optvar="configfile")
218
219     pacman = arizonaconfig.get_option("pacman")
220     verbose = arizonaconfig.get_option("pacmanverbose")
221     sync = arizonaconfig.get_option("pacmandsync")
222     timer = arizonaconfig.get_option("pactimer")
223
224     if verbose == True:
225        verbose = 1
226        print "[pacmand.plush] Will not be verbose"
227     else:
228        verbose = 0
229        print "[pacmand.plush] Will be verbose"
230
231     verbose = 1
232     pollfilename = arizonaconfig.get_option("pollfile")
233     heartbeat = arizonaconfig.get_option("heartbeat")
234
235     arizonareport.send_syslog( arizonareport.INFO, "[pacmand.plush] Will monitor \"" + pollfilename + "\" for changes")
236
237     if heartbeat:
238        arizonareport.send_syslog( arizonareport.INFO, "[pacmand.plush] Heartbeat mode activated; will invoke pacman at regular intervals")
239        print "heartbeat"
240
241     if verbose != 0:
242        arizonareport.send_syslog(arizonareport.INFO, "[pacmand.plush] before create event")
243        print "[pacmand.plush] before create event"
244
245     # create an event used to signal pacman updates
246     pacman_update_event = threading.Event()
247     pacman_update_event.set()
248
249     glo_cmd = "%s" % (pacman)
250     if verbose != 0:
251        print "[pacmand.plush] pacman command is " + glo_cmd
252
253     # set the hangup signal handler
254     signal.signal(signal.SIGHUP, handler_sighup)
255
256     if verbose != 0:
257         print "[pacmand.plush] set SIGHUP handler"
258
259     if sync:
260         arizonareport.send_syslog(arizonareport.INFO, "[pacmand.plush] Starting")
261         if verbose != 0:
262            print "[pacmand.plush] Starting"
263     else:
264         # run as a daemon
265         arizonageneral.make_daemon("pacmand.plush")
266         if verbose != 0:
267            print "[pacmand.plush] daemonized"
268
269     last_time = 0
270
271     # update no more often than once every 30 seconds (if we have a heartbeat)
272     minimum_time_since_last = 30
273
274     if verbose != 0:
275        arizonareport.send_syslog(arizonareport.INFO, "[pacmand.plush] before enter loop")
276        print "[pacmand.plush] About to enter main loop"
277
278     # if we're supposed to poll for metadata via a file, then start doing so
279     if pollfilename != "":
280         filepoller_verbosity = 0
281         if verbose != 0:
282             filepoller_verbosity = 1
283             print "[pacmand.plush] forking thread to monitor " + pollfilename
284         filepoller = FilePoller(pollfilename, filepoller_verbosity)
285     else:
286         filepoller = None
287         print "[pacmand.plush] will not poll files"
288
289     while True:
290
291         # if we have a heartbeat, then invoke pacman every $minimum_time_since_last seconds
292         if heartbeat == True:
293             # check and see if we just ran. If we did, then delay a bit
294             time_since_last = time.time() - last_time
295         
296             if time_since_last < minimum_time_since_last:
297                delay = minimum_time_since_last - time_since_last
298                if verbose != 0:
299                    arizonareport.send_syslog(arizonareport.INFO, "[pacmand.plush] updates too frequent; delaying " + str(delay))
300                    print "[pacmand.plush] updates too frequent; delaying " + str(delay)
301                time.sleep(delay)
302
303             # block if an initscript is running
304             wait_if_initscript_running()
305
306             time_since_last = time.time() - last_time
307
308             print "[pacmand.plush] time since last run " + str(int(time_since_last)) + " seconds"
309             arizonareport.send_syslog(arizonareport.INFO, "[pacmand] time since last run " + str(int(time_since_last)) + " seconds")
310
311             last_time = time.time()
312
313             # clear the update event, so that it can be signalled again
314             pacman_update_event.clear()
315
316             # invoke pacman
317             pacman_update()
318         # end heartbeat
319         else:
320            time.sleep(minimum_time_since_last)
321
322         # sleep and wait for another interval if there is no file
323         if pollfilename == "":
324             if verbose != 0:
325                print "[pacmand.plush] sleeping for " + str(timer) + " seconds"
326                arizonareport.send_syslog(arizonareport.INFO, "[pacmand.plush] sleeping for " + str(timer) + " seconds")
327             time.sleep(timer)
328
329
330
331 if __name__ == "__main__":
332     Main()