diff --git a/lib/asyncnotifier.py b/lib/asyncnotifier.py index b576a5d6f566f6624707ce30d1bcb454b3e61580..421e476dba9de643556d934ccc7482e6e0952fb1 100644 --- a/lib/asyncnotifier.py +++ b/lib/asyncnotifier.py @@ -113,20 +113,17 @@ class SingleFileEventHandler(pyinotify.ProcessEvent): # pylint: disable-msg=C0103 # this overrides a method in pyinotify.ProcessEvent def process_IN_IGNORED(self, event): - # Due to the fact that we monitor just for the cluster config file (rather - # than for the whole data dir) when the file is replaced with another one - # (which is what happens normally in ganeti) we're going to receive an - # IN_IGNORED event from inotify, because of the file removal (which is - # contextual with the replacement). In such a case we need to create - # another watcher for the "new" file. + # Since we monitor a single file rather than the directory it resides in, + # when that file is replaced with another one (which is what happens when + # utils.WriteFile, the most normal way of updating files in ganeti, is + # called) we're going to receive an IN_IGNORED event from inotify, because + # of the file removal (which is contextual with the replacement). In such a + # case we'll need to create a watcher for the "new" file. This can be done + # by the callback by calling "enable" again on us. logging.debug("Received 'ignored' inotify event for %s", event.path) self.watch_handle = None try: - # Since the kernel believes the file we were interested in is gone, it's - # not going to notify us of any other events, until we set up, here, the - # new watch. This is not a race condition, though, since we're anyway - # going to realod the file after setting up the new watch. self.callback(False) except: # pylint: disable-msg=W0702 # we need to catch any exception here, log it, but proceed, because even @@ -137,10 +134,10 @@ class SingleFileEventHandler(pyinotify.ProcessEvent): # pylint: disable-msg=C0103 # this overrides a method in pyinotify.ProcessEvent def process_IN_MODIFY(self, event): - # This gets called when the config file is modified. Note that this doesn't - # usually happen in Ganeti, as the config file is normally replaced by a - # new one, at filesystem level, rather than actually modified (see - # utils.WriteFile) + # This gets called when the monitored file is modified. Note that this + # doesn't usually happen in Ganeti, as most of the time we're just + # replacing any file with a new one, at filesystem level, rather than + # actually changing it. (see utils.WriteFile) logging.debug("Received 'modify' inotify event for %s", event.path) try: diff --git a/lib/daemon.py b/lib/daemon.py index 2fdaa76a272c7675fda3047315e16ed338b4e57b..8a057a0f3cf83b08a9cc8a3817815a7f6c1367fb 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -168,6 +168,7 @@ class Mainloop(object): @utils.SignalHandled([signal.SIGCHLD]) @utils.SignalHandled([signal.SIGTERM]) + @utils.SignalHandled([signal.SIGINT]) def Run(self, signal_handlers=None): """Runs the mainloop. @@ -194,7 +195,7 @@ class Mainloop(object): handler = signal_handlers[sig] if handler.called: self._CallSignalWaiters(sig) - running = (sig != signal.SIGTERM) + running = sig not in (signal.SIGTERM, signal.SIGINT) handler.Clear() def _CallSignalWaiters(self, signum): diff --git a/test/ganeti.asyncnotifier_unittest.py b/test/ganeti.asyncnotifier_unittest.py index f487af77259d9bf9a8cf033babe9e00a4ce8b60e..b379ba1efff8d77f459938d8a8f4f6552be3c995 100755 --- a/test/ganeti.asyncnotifier_unittest.py +++ b/test/ganeti.asyncnotifier_unittest.py @@ -41,79 +41,80 @@ import testutils class TestSingleFileEventHandler(testutils.GanetiTestCase): """Test daemon.Mainloop""" + NOTIFIERS = [NOTIFIER_TERM, NOTIFIER_NORM] = range(2) + def setUp(self): testutils.GanetiTestCase.setUp(self) self.mainloop = daemon.Mainloop() - notifier_count = 2 - self.chk_files = [self._CreateTempFile() for i in range(notifier_count)] - self.notified = [False for i in range(notifier_count)] + self.chk_files = [self._CreateTempFile() for i in self.NOTIFIERS] + self.notified = [False for i in self.NOTIFIERS] # We need one watch manager per notifier, as those contain the file # descriptor which is monitored by asyncore - self.wms = [pyinotify.WatchManager() for i in range(notifier_count)] - self.cbk = [self.OnInotifyCallback(self.notified, i) - for i in range(notifier_count)] + self.wms = [pyinotify.WatchManager() for i in self.NOTIFIERS] + self.cbk = [self.OnInotifyCallback(self, i) + for i in range(len(self.NOTIFIERS))] self.ihandler = [asyncnotifier.SingleFileEventHandler(self.wms[i], self.cbk[i], self.chk_files[i]) - for i in range(notifier_count)] + for i in range(len(self.NOTIFIERS))] self.notifiers = [asyncnotifier.AsyncNotifier(self.wms[i], self.ihandler[i]) - for i in range(notifier_count)] - # notifier 0 is enabled by default, as we use it to get out of the loop - self.ihandler[0].enable() + for i in range(len(self.NOTIFIERS))] + # TERM notifier is enabled by default, as we use it to get out of the loop + self.ihandler[self.NOTIFIER_TERM].enable() class OnInotifyCallback: - def __init__(self, notified, i): - self.notified = notified + def __init__(self, testobj, i): + self.testobj = testobj + self.notified = testobj.notified self.i = i def __call__(self, enabled): self.notified[self.i] = True - # notifier 0 is special as we use it to terminate the mainloop - if self.i == 0: + if self.i == self.testobj.NOTIFIER_TERM: os.kill(os.getpid(), signal.SIGTERM) def testReplace(self): - utils.WriteFile(self.chk_files[0], data="dummy") + utils.WriteFile(self.chk_files[self.NOTIFIER_TERM], data="dummy") self.mainloop.Run() - self.assert_(self.notified[0]) - self.assert_(not self.notified[1]) + self.assert_(self.notified[self.NOTIFIER_TERM]) + self.assert_(not self.notified[self.NOTIFIER_NORM]) def testEnableDisable(self): - self.ihandler[0].enable() - self.ihandler[0].disable() - self.ihandler[0].disable() - self.ihandler[0].enable() - self.ihandler[0].disable() - self.ihandler[0].enable() - utils.WriteFile(self.chk_files[0], data="dummy") + self.ihandler[self.NOTIFIER_TERM].enable() + self.ihandler[self.NOTIFIER_TERM].disable() + self.ihandler[self.NOTIFIER_TERM].disable() + self.ihandler[self.NOTIFIER_TERM].enable() + self.ihandler[self.NOTIFIER_TERM].disable() + self.ihandler[self.NOTIFIER_TERM].enable() + utils.WriteFile(self.chk_files[self.NOTIFIER_TERM], data="dummy") self.mainloop.Run() - self.assert_(self.notified[0]) - self.assert_(not self.notified[1]) + self.assert_(self.notified[self.NOTIFIER_TERM]) + self.assert_(not self.notified[self.NOTIFIER_NORM]) def testDoubleEnable(self): - self.ihandler[0].enable() - self.ihandler[0].enable() - utils.WriteFile(self.chk_files[0], data="dummy") + self.ihandler[self.NOTIFIER_TERM].enable() + self.ihandler[self.NOTIFIER_TERM].enable() + utils.WriteFile(self.chk_files[self.NOTIFIER_TERM], data="dummy") self.mainloop.Run() - self.assert_(self.notified[0]) - self.assert_(not self.notified[1]) + self.assert_(self.notified[self.NOTIFIER_TERM]) + self.assert_(not self.notified[self.NOTIFIER_NORM]) def testDefaultDisabled(self): - utils.WriteFile(self.chk_files[1], data="dummy") - utils.WriteFile(self.chk_files[0], data="dummy") + utils.WriteFile(self.chk_files[self.NOTIFIER_NORM], data="dummy") + utils.WriteFile(self.chk_files[self.NOTIFIER_TERM], data="dummy") self.mainloop.Run() - self.assert_(self.notified[0]) - # notifier 1 is disabled by default - self.assert_(not self.notified[1]) + self.assert_(self.notified[self.NOTIFIER_TERM]) + # NORM notifier is disabled by default + self.assert_(not self.notified[self.NOTIFIER_NORM]) def testBothEnabled(self): - self.ihandler[1].enable() - utils.WriteFile(self.chk_files[1], data="dummy") - utils.WriteFile(self.chk_files[0], data="dummy") + self.ihandler[self.NOTIFIER_NORM].enable() + utils.WriteFile(self.chk_files[self.NOTIFIER_NORM], data="dummy") + utils.WriteFile(self.chk_files[self.NOTIFIER_TERM], data="dummy") self.mainloop.Run() - self.assert_(self.notified[0]) - self.assert_(self.notified[1]) + self.assert_(self.notified[self.NOTIFIER_TERM]) + self.assert_(self.notified[self.NOTIFIER_NORM]) if __name__ == "__main__": diff --git a/test/ganeti.daemon_unittest.py b/test/ganeti.daemon_unittest.py index ad5a12852904f2c68f7ae1f635afd833c4753a67..81912ee181af412eb3288b09010dc6191fd4c2b1 100755 --- a/test/ganeti.daemon_unittest.py +++ b/test/ganeti.daemon_unittest.py @@ -56,6 +56,16 @@ class TestMainloop(testutils.GanetiTestCase): self.mainloop.Run() # terminates by _SendSig being scheduled self.assertEquals(self.sendsig_events, [signal.SIGTERM]) + def testTerminatingSignals(self): + self.mainloop.scheduler.enter(0.1, 1, self._SendSig, [signal.SIGCHLD]) + self.mainloop.scheduler.enter(0.2, 1, self._SendSig, [signal.SIGINT]) + self.mainloop.Run() + self.assertEquals(self.sendsig_events, [signal.SIGCHLD, signal.SIGINT]) + self.mainloop.scheduler.enter(0.1, 1, self._SendSig, [signal.SIGTERM]) + self.mainloop.Run() + self.assertEquals(self.sendsig_events, [signal.SIGCHLD, signal.SIGINT, + signal.SIGTERM]) + def testSchedulerCancel(self): handle = self.mainloop.scheduler.enter(0.1, 1, self._SendSig, [signal.SIGTERM])