diff --git a/lib/utils.py b/lib/utils.py index a9b71b5fbb3c0161d298dc0ead40a5f0027b8588..6744358222ce075825914cd0ac502a82d3f02c46 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -1092,6 +1092,35 @@ def RemovePidFile(name): pass +def KillProcess(pid, signal=signal.SIGTERM, timeout=30): + """Kill a process given by its pid. + + @type pid: int + @param pid: The PID to terminate. + @type signal: int + @param signal: The signal to send, by default SIGTERM + @type timeout: int + @param timeout: The timeout after which, if the process is still alive, + a SIGKILL will be sent. If not positive, no such checking + will be done + + """ + if pid <= 0: + # kill with pid=0 == suicide + raise errors.ProgrammerError("Invalid pid given '%s'" % pid) + + if not IsProcessAlive(pid): + return + os.kill(pid, signal) + if timeout <= 0: + return + end = time.time() + timeout + while time.time() < end and IsProcessAlive(pid): + time.sleep(0.1) + if IsProcessAlive(pid): + os.kill(pid, signal.SIGKILL) + + def FindFile(name, search_path, test=os.path.exists): """Look for a filesystem object in a given path. diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index 23a8b6b75fb52f27e31bf70b18895131d66a80b0..9a9eed94880bc5cbfbb519e78605f25134e863de 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -43,7 +43,8 @@ from ganeti.utils import IsProcessAlive, RunCmd, \ ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \ ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \ SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree -from ganeti.errors import LockError, UnitParseError, GenericError +from ganeti.errors import LockError, UnitParseError, GenericError, \ + ProgrammerError def _ChildHandler(signal, stack): global _ChildFlag @@ -132,6 +133,27 @@ class TestPidFileFunctions(unittest.TestCase): self.failIf(os.path.exists(pid_file), "PID file should not exist anymore") + def testKill(self): + pid_file = self.f_dpn('child') + r_fd, w_fd = os.pipe() + new_pid = os.fork() + if new_pid == 0: #child + utils.WritePidFile('child') + os.write(w_fd, 'a') + signal.pause() + os._exit(0) + return + # else we are in the parent + # wait until the child has written the pid file + os.read(r_fd, 1) + read_pid = utils.ReadPidFile(pid_file) + self.failUnlessEqual(read_pid, new_pid) + self.failUnless(utils.IsProcessAlive(new_pid)) + utils.KillProcess(new_pid) + self.failIf(utils.IsProcessAlive(new_pid)) + utils.RemovePidFile('child') + self.failUnlessRaises(ProgrammerError, utils.KillProcess, 0) + def tearDown(self): for name in os.listdir(self.dir): os.unlink(os.path.join(self.dir, name))