From b2a1f5119b35d77b9c72daef546c5622b457e012 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Tue, 29 Jul 2008 08:49:50 +0000
Subject: [PATCH] Add a KillProcess function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We cannot depend on all environments to have a start-stop-daemon or
similar tool. We instead implement a KillProcess function that behaves
similar to β€œstart-stop-daemon --retry”.

Note that the attached unittest can hang in foreground if the child
misbehaves (doesn't write to the internal pipe). Since unittest are
either run in the foreground or are run with a timeout from an automated
framework, I think this is an acceptable trade-off (against of using
hardcoded timeouts in the test).

Reviewed-by: imsnah
---
 lib/utils.py                  | 29 +++++++++++++++++++++++++++++
 test/ganeti.utils_unittest.py | 24 +++++++++++++++++++++++-
 2 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/lib/utils.py b/lib/utils.py
index a9b71b5fb..674435822 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 23a8b6b75..9a9eed948 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))
-- 
GitLab