[tor-commits] [stem/master] Adding get_pid and get_bsd_jail_id util functoins

atagar at torproject.org atagar at torproject.org
Mon Oct 17 02:33:38 UTC 2011


commit aa01c0798960a520ac1da81e5a1a447c29dbb962
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Oct 16 19:23:57 2011 -0700

    Adding get_pid and get_bsd_jail_id util functoins
    
    Stealing arm's getTorPid and getBsdJailId from the torTools util, generalizing
    both functions to be for arbitrary processes rather than just tor. This also
    adds unit tests for get_pid and a simple exercise of get_bsd_jail_id (I can't
    really test the later since I'm not on BSD).
---
 run_tests.py         |   11 +++-
 stem/util/system.py  |  162 ++++++++++++++++++++++++++++++++++++++++++++++++++
 test/integ/runner.py |   29 ++++++++-
 test/integ/system.py |   20 ++++++
 4 files changed, 217 insertions(+), 5 deletions(-)

diff --git a/run_tests.py b/run_tests.py
index 4f3eee7..284f48c 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -5,6 +5,7 @@ Runs unit and integration tests.
 """
 
 import sys
+import time
 import getopt
 import unittest
 import test.unit.message
@@ -52,6 +53,7 @@ Runs tests for the stem library.
 """
 
 if __name__ == '__main__':
+  start_time = time.time()
   run_unit_tests = False
   run_integ_tests = False
   integ_targets = TARGETS.values()
@@ -111,20 +113,20 @@ if __name__ == '__main__':
   if run_integ_tests:
     print "%s\n%s\n%s\n" % (DIVIDER, "INTEGRATION TESTS".center(70), DIVIDER)
     
-    integ_runner = test.integ.runner.Runner()
+    integ_runner = test.integ.runner.get_runner()
     
     try:
       integ_runner.run_setup()
       integ_runner.start()
       
       print term.format("Running tests...", term.Color.BLUE, term.Attr.BOLD)
+      print
+      
       for name, test_class in INTEG_TESTS:
         print "%s\n%s\n%s\n" % (DIVIDER, name, DIVIDER)
         suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
         unittest.TextTestRunner(verbosity=2).run(suite)
         print
-    
-      print
     except OSError:
       pass
     finally:
@@ -144,4 +146,7 @@ if __name__ == '__main__':
     #    print "  %s" % term.format("Unimplemented", term.Color.RED, term.Attr.BOLD)
     #  
     #  print ""
+  
+  print term.format("Testing Completed (%i seconds)" % (time.time() - start_time), term.Color.GREEN, term.Attr.BOLD)
+  print
 
diff --git a/stem/util/system.py b/stem/util/system.py
index bdada9b..746eed4 100644
--- a/stem/util/system.py
+++ b/stem/util/system.py
@@ -89,6 +89,168 @@ def is_running(command, suppress_exc = True):
     if suppress_exc: return None
     else: raise OSError("Unable to check via 'ps -A co command'")
 
+def get_pid(process_name, process_port = None):
+  """
+  Attempts to determine the process id for a running process, using the
+  following:
+  
+  1. "pgrep -x <name>"
+  2. "pidof <name>"
+  3. "netstat -npl | grep 127.0.0.1:<port>"
+  4. "ps -o pid -C <name>"
+  5. "sockstat -4l -P tcp -p <port> | grep <name>"
+  6. "ps axc | egrep \" <name>$\""
+  7. "lsof -wnPi | egrep \"^<name>.*:<port>\""
+  
+  If pidof or ps provide multiple instance of the process then their results
+  are discarded (since only netstat can differentiate using a bound port).
+  
+  Arguments:
+    process_name (str) - process name for which to fetch the pid
+    process_port (int) - port that the process we're interested in is bound to,
+                         this is used to disambiguate if there's multiple
+                         instances running
+  
+  Returns:
+    int with the process id, None if either no running process exists or it
+    can't be determined
+  """
+  
+  # attempts to resolve using pgrep, failing if:
+  # - the process is running under a different name
+  # - there are multiple instances
+  
+  try:
+    results = call("pgrep -x %s" % process_name)
+    
+    if len(results) == 1 and len(results[0].split()) == 1:
+      pid = results[0].strip()
+      if pid.isdigit(): return int(pid)
+  except IOError: pass
+  
+  # attempts to resolve using pidof, failing if:
+  # - the process is running under a different name
+  # - there are multiple instances
+  
+  try:
+    results = call("pidof %s" % process_name)
+    
+    if len(results) == 1 and len(results[0].split()) == 1:
+      pid = results[0].strip()
+      if pid.isdigit(): return int(pid)
+  except IOError: pass
+  
+  # attempts to resolve using netstat, failing if:
+  # - the process being run as a different user due to permissions
+  
+  if process_port:
+    try:
+      results = call("netstat -npl | grep 127.0.0.1:%i" % process_port)
+      
+      if len(results) == 1:
+        results = results[0].split()[6] # process field (ex. "7184/tor")
+        pid = results[:results.find("/")]
+        if pid.isdigit(): return int(pid)
+    except IOError: pass
+  
+  # attempts to resolve using ps, failing if:
+  # - the process is running under a different name
+  # - there are multiple instances
+  
+  try:
+    results = call("ps -o pid -C %s" % process_name)
+    
+    if len(results) == 2:
+      pid = results[1].strip()
+      if pid.isdigit(): return int(pid)
+  except IOError: pass
+  
+  # attempts to resolve using sockstat, failing if:
+  # - sockstat doesn't accept the -4 flag (BSD only)
+  # - the process is running under a different name
+  # - there are multiple instances using the same port on different addresses
+  # 
+  # TODO: The later two issues could be solved by filtering for an expected IP
+  # address instead of the process name.
+  
+  if process_port:
+    try:
+      results = call("sockstat -4l -P tcp -p %i | grep %s" % (process_port, process_name))
+      
+      if len(results) == 1 and len(results[0].split()) == 7:
+        pid = results[0].split()[2]
+        if pid.isdigit(): return int(pid)
+    except IOError: pass
+  
+  # attempts to resolve via a ps command that works on mac/bsd (this and lsof
+  # are the only resolvers to work on that platform). This fails if:
+  # - the process is running under a different name
+  # - there are multiple instances
+  
+  try:
+    results = call("ps axc | egrep \" %s$\"" % process_name)
+    
+    if len(results) == 1 and len(results[0].split()) > 0:
+      pid = results[0].split()[0]
+      if pid.isdigit(): return int(pid)
+  except IOError: pass
+  
+  # attempts to resolve via lsof, this should work on linux, mac, and bsd
+  # and only fail if:
+  # - the process is running under a different name
+  # - the process being run as a different user due to permissions
+  # - there are multiple instances using the same port on different addresses
+  
+  try:
+    port_comp = str(process_port) if process_port else ""
+    results = call("lsof -wnPi | egrep \"^%s.*:%s\"" % (process_name, port_comp))
+    
+    # This can result in multiple entries with the same pid (from the query
+    # itself). Checking all lines to see if they're in agreement about the pid.
+    
+    if results:
+      pid = ""
+      
+      for line in results:
+        line_comp = line.split()
+        
+        if len(line_comp) >= 2 and (not pid or line_comp[1] == pid):
+          pid = line_comp[1]
+        else: raise IOError
+      
+      if pid.isdigit(): return int(pid)
+  except IOError: pass
+  
+  return None
+
+def get_bsd_jail_id(pid):
+  """
+  Get the FreeBSD jail id for a process.
+  
+  Arguments:
+    pid (int) - process id of the jail id to be queried
+  
+  Returns:
+    int for the jail id, zero if this can't be determined
+  """
+  
+  # Output when called from a FreeBSD jail or when Tor isn't jailed:
+  #   JID
+  #    0
+  # 
+  # Otherwise it's something like:
+  #   JID
+  #    1
+  
+  ps_output = call("ps -p %s -o jid" % pid)
+  
+  if len(ps_output) == 2 and len(ps_output[1].split()) == 1:
+    jid = ps_output[1].strip()
+    if jid.isdigit(): return int(jid)
+  
+  log.log(log.WARN, "Failed to figure out the FreeBSD jail id. Assuming 0.")
+  return 0
+
 def call(command, suppress_exc = True):
   """
   Issues a command in a subprocess, blocking until completion and returning the
diff --git a/test/integ/runner.py b/test/integ/runner.py
index 4805c20..c83fd2d 100644
--- a/test/integ/runner.py
+++ b/test/integ/runner.py
@@ -12,13 +12,25 @@ import subprocess
 from stem.util import term
 
 # number of seconds before we time out our attempt to start a tor instance
-TOR_INIT_TIMEOUT = 30
+TOR_INIT_TIMEOUT = 60
 
 BASIC_TORRC = """# configuration for stem integration tests
 DataDirectory %s
 ControlPort 1111
 """
 
+# singleton Runner instance
+INTEG_RUNNER = None
+
+def get_runner():
+  """
+  Singleton for the runtime context of integration tests.
+  """
+  
+  global INTEG_RUNNER
+  if not INTEG_RUNNER: INTEG_RUNNER = Runner()
+  return INTEG_RUNNER
+
 class Runner:
   def __init__(self):
     self._test_dir = tempfile.mktemp("-stem-integ")
@@ -125,8 +137,9 @@ class Runner:
     if self._tor_process:
       sys.stdout.write(term.format("Shutting down tor... ", term.Color.BLUE, term.Attr.BOLD))
       self._tor_process.kill()
+      self._tor_process.communicate() # blocks until the process is done
+      self._tor_process = None
       sys.stdout.write(term.format("done\n", term.Color.BLUE, term.Attr.BOLD))
-      print # extra newline
   
   def get_pid(self):
     """
@@ -139,4 +152,16 @@ class Runner:
     if self._tor_process:
       return self._tor_process.pid
     else: return None
+  
+  def get_control_port(self):
+    """
+    Provides the control port tor is running with.
+    
+    Returns:
+      int for the port tor's controller interface is bound to, None if it
+      doesn't have one
+    """
+    
+    # TODO: this will be fetched from torrc contents when we use custom configs
+    return 1111
 
diff --git a/test/integ/system.py b/test/integ/system.py
index 6b38615..edc0291 100644
--- a/test/integ/system.py
+++ b/test/integ/system.py
@@ -3,6 +3,7 @@ Unit tests for the util.system functions in the context of a tor process.
 """
 
 import unittest
+import test.integ.runner
 
 from stem.util import system
 
@@ -30,3 +31,22 @@ class TestSystemFunctions(unittest.TestCase):
     self.assertTrue(system.is_running("tor"))
     self.assertFalse(system.is_running("blarg_and_stuff"))
 
+  def test_get_pid(self):
+    """
+    Checks the util.system.get_pid function.
+    """
+    
+    runner = test.integ.runner.get_runner()
+    self.assertEquals(runner.get_pid(), system.get_pid("tor"))
+    self.assertEquals(runner.get_pid(), system.get_pid("tor", runner.get_control_port()))
+    self.assertEquals(None, system.get_pid("blarg_and_stuff"))
+  
+  def test_get_bsd_jail_id(self):
+    """
+    Exercises the util.system.get_bsd_jail_id function, running through the
+    failure case (since I'm not on BSD I can't really test this function
+    properly).
+    """
+    
+    self.assertEquals(0, system.get_bsd_jail_id("blarg_and_stuff"))
+





More information about the tor-commits mailing list