[tor-commits] [stem/master] Controller method to query tor's pid
atagar at torproject.org
atagar at torproject.org
Tue May 28 04:24:14 UTC 2013
commit 0f7d5d118d83db000dd57b646ba91d4152c44e91
Author: Damian Johnson <atagar at torproject.org>
Date: Mon May 27 21:20:42 2013 -0700
Controller method to query tor's pid
Adding a get_pid() method to the controller to make it simpler to figure out
its pid. This attempts resolution via the PidFile, process name, control port,
and control socket file.
---
docs/change_log.rst | 1 +
stem/control.py | 55 ++++++++++++++++++++++++
test/unit/control/controller.py | 89 ++++++++++++++++++++++++++++++++-------
3 files changed, 130 insertions(+), 15 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index d485071..cb0134e 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -40,6 +40,7 @@ The following are only available within stem's `git repository
* **Controller**
+ * Added a :class:`~stem.control.Controller` method for querying tor's pid (:func:`~stem.control.Controller.get_pid`)
* :class:`~stem.response.events.AddrMapEvent` support for the new CACHED argument (:trac:`8596`, :spec:`25b0d43`)
* :func:`~stem.control.Controller.attach_stream` could encounter an undocumented 555 response (:trac:`8701`, :spec:`7286576`)
* :class:`~stem.descriptor.server_descriptor.RelayDescriptor` digest validation was broken when dealing with non-unicode content with python 3 (:trac:`8755`)
diff --git a/stem/control.py b/stem/control.py
index 6ec0006..a1a7ae4 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -24,6 +24,7 @@ providing its own for interacting at a higher level.
|- get_exit_policy - provides our exit policy
|- get_socks_listeners - provides where tor is listening for SOCKS connections
|- get_protocolinfo - information about the controller interface
+ |- get_pid - provides the pid of our tor process
|
|- get_microdescriptor - querying the microdescriptor for a relay
|- get_microdescriptors - provides all presently available microdescriptors
@@ -152,6 +153,7 @@ import stem.socket
import stem.util.connection
import stem.util.enum
import stem.util.str_tools
+import stem.util.system
import stem.util.tor_tools
import stem.version
@@ -992,6 +994,59 @@ class Controller(BaseController):
else:
return default
+ def get_pid(self, default = UNDEFINED):
+ """
+ Provides the process id of tor. This only works if tor is running locally.
+ Also, most of its checks are platform dependent, and hence are not entirely
+ reliable.
+
+ :param object default: response if the query fails
+
+ :returns: int with our process' pid
+
+ :raises: **ValueError** if unable to determine the pid and no default was
+ provided
+ """
+
+ if not self.get_socket().is_localhost():
+ if default == UNDEFINED:
+ raise ValueError("Tor isn't running locally")
+ else:
+ return default
+
+ pid = self._get_cache("pid")
+
+ if not pid:
+ pid_file_path = self.get_conf("PidFile", None)
+
+ if pid_file_path is not None:
+ with open(pid_file_path) as pid_file:
+ pid_file_contents = pid_file.read().strip()
+
+ if pid_file_contents.isdigit():
+ pid = int(pid_file_contents)
+
+ if not pid:
+ pid = stem.util.system.get_pid_by_name('tor')
+
+ if not pid:
+ control_socket = self.get_socket()
+
+ if isinstance(control_socket, stem.socket.ControlPort):
+ pid = stem.util.system.get_pid_by_port(control_socket.get_port())
+ elif isinstance(control_socket, stem.socket.ControlSocketFile):
+ pid = stem.util.system.get_pid_by_open_file(control_socket.get_socket_path())
+
+ if pid and self.is_caching_enabled():
+ self._set_cache({"pid": pid})
+
+ if pid:
+ return pid
+ elif default == UNDEFINED:
+ raise ValueError("Unable to resolve tor's pid")
+ else:
+ return default
+
def get_microdescriptor(self, relay, default = UNDEFINED):
"""
Provides the microdescriptor for the relay with the given fingerprint or
diff --git a/test/unit/control/controller.py b/test/unit/control/controller.py
index 9efe4bf..ab89ac0 100644
--- a/test/unit/control/controller.py
+++ b/test/unit/control/controller.py
@@ -3,16 +3,20 @@ Unit tests for the stem.control module. The module's primarily exercised via
integ tests, but a few bits lend themselves to unit testing.
"""
+import os
+import tempfile
import unittest
import stem.descriptor.router_status_entry
import stem.response
import stem.socket
+import stem.util.system
import stem.version
from stem import InvalidArguments, InvalidRequest, ProtocolError, UnsatisfiableRequest
from stem.control import _parse_circ_path, Controller, EventType
from stem.exit_policy import ExitPolicy
+from stem.socket import ControlSocket
from test import mocking
@@ -215,63 +219,118 @@ class TestControl(unittest.TestCase):
Exercises the get_protocolinfo() method.
"""
- # Use the handy mocked protocolinfo response.
+ # use the handy mocked protocolinfo response
+
mocking.mock(stem.connection.get_protocolinfo, mocking.return_value(
mocking.get_protocolinfo_response()
))
- # Compare the str representation of these object, because the class
- # does not have, nor need, a direct comparison operator.
- self.assertEqual(str(mocking.get_protocolinfo_response()), str(self.controller.get_protocolinfo()))
- # Raise an exception in the stem.connection.get_protocolinfo() call.
+ # compare the str representation of these object, because the class
+ # does not have, nor need, a direct comparison operator
+
+ self.assertEqual(
+ str(mocking.get_protocolinfo_response()),
+ str(self.controller.get_protocolinfo())
+ )
+
+ # raise an exception in the stem.connection.get_protocolinfo() call
+
mocking.mock(stem.connection.get_protocolinfo, mocking.raise_exception(ProtocolError))
- # Get a default value when the call fails.
+ # get a default value when the call fails
self.assertEqual(
"default returned",
self.controller.get_protocolinfo(default = "default returned")
)
- # No default value, accept the error.
+ # no default value, accept the error
+
self.assertRaises(ProtocolError, self.controller.get_protocolinfo)
+ def test_get_pid_remote(self):
+ """
+ Exercise the get_pid() method for a non-local socket.
+ """
+
+ mocking.mock_method(ControlSocket, "is_localhost", mocking.return_false())
+
+ self.assertRaises(ValueError, self.controller.get_pid)
+ self.assertEqual(123, self.controller.get_pid(123))
+
+ def test_get_pid_by_pid_file(self):
+ """
+ Exercise the get_pid() resolution via a PidFile.
+ """
+
+ # It's a little inappropriate for us to be using tempfile in unit tests,
+ # but this is more reliable than trying to mock open().
+
+ mocking.mock_method(ControlSocket, "is_localhost", mocking.return_true())
+
+ pid_file_path = tempfile.mkstemp()[1]
+
+ try:
+ with open(pid_file_path, 'w') as pid_file:
+ pid_file.write('321')
+
+ mocking.mock_method(Controller, "get_conf", mocking.return_value(pid_file_path))
+ self.assertEqual(321, self.controller.get_pid())
+ finally:
+ os.remove(pid_file_path)
+
+ def test_get_pid_by_name(self):
+ """
+ Exercise the get_pid() resolution via the process name.
+ """
+
+ mocking.mock_method(ControlSocket, "is_localhost", mocking.return_true())
+ mocking.mock(stem.util.system.get_pid_by_name, mocking.return_value(432))
+ self.assertEqual(432, self.controller.get_pid())
+
def test_get_network_status(self):
"""
Exercises the get_network_status() method.
"""
- # Build a single router status entry.
+ # build a single router status entry
+
nickname = "Beaver"
fingerprint = "/96bKo4soysolMgKn5Hex2nyFSY"
desc = "r %s %s u5lTXJKGsLKufRLnSyVqT7TdGYw 2012-12-30 22:02:49 77.223.43.54 9001 0\ns Fast Named Running Stable Valid\nw Bandwidth=75" % (nickname, fingerprint)
router = stem.descriptor.router_status_entry.RouterStatusEntryV2(desc)
- # Always return the same router status entry.
+ # always return the same router status entry
+
mocking.mock_method(Controller, "get_info", mocking.return_value(desc))
- # Pretend to get the router status entry with its name.
+ # pretend to get the router status entry with its name
+
self.assertEqual(router, self.controller.get_network_status(nickname))
- # Pretend to get the router status entry with its fingerprint.
+ # pretend to get the router status entry with its fingerprint
+
hex_fingerprint = stem.descriptor.router_status_entry._base64_to_hex(fingerprint, False)
self.assertEqual(router, self.controller.get_network_status(hex_fingerprint))
- # Mangle hex fingerprint and try again.
+ # mangle hex fingerprint and try again
+
hex_fingerprint = hex_fingerprint[2:]
self.assertRaises(ValueError, self.controller.get_network_status, hex_fingerprint)
- # Raise an exception in the get_info() call.
+ # raise an exception in the get_info() call
+
mocking.mock_method(Controller, "get_info", mocking.raise_exception(InvalidArguments))
- # Get a default value when the call fails.
+ # get a default value when the call fails
self.assertEqual(
"default returned",
self.controller.get_network_status(nickname, default = "default returned")
)
- # No default value, accept the error.
+ # no default value, accept the error
+
self.assertRaises(InvalidArguments, self.controller.get_network_status, nickname)
def test_event_listening(self):
More information about the tor-commits
mailing list