commit f5c7c226321f801e448f759e2ba3617c0ef5814b
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Mar 2 19:28:06 2013 -0800
Adding get_microdescriptor() method to the Controller
Adding a method to query individual microdescriptors. This is very similar to
its server descriptor and network status counterparts.
---
stem/control.py | 37 ++++++++++++++++++++++++++++++++++++
stem/descriptor/microdescriptor.py | 15 ++++++++++++++
test/integ/control/controller.py | 37 ++++++++++++++++++++++++++++++++++++
3 files changed, 89 insertions(+), 0 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index 77e78c6..d1025b8 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -25,6 +25,7 @@ providing its own for interacting at a higher level.
|- get_socks_listeners - provides where tor is listening for SOCKS connections
|- get_protocolinfo - information about the controller interface
|
+ |- get_microdescriptor - querying the microdescriptor for a relay
|- get_server_descriptor - querying the server descriptor for a relay
|- get_server_descriptors - provides all presently available server descriptors
|- get_network_status - querying the router status entry for a relay
@@ -139,6 +140,7 @@ import StringIO
import threading
import time
+import stem.descriptor.microdescriptor
import stem.descriptor.router_status_entry
import stem.descriptor.server_descriptor
import stem.exit_policy
@@ -962,6 +964,41 @@ class Controller(BaseController):
else:
return default
+ def get_microdescriptor(self, relay, default = UNDEFINED):
+ """
+ Provides the microdescriptor for the relay with the given fingerprint or
+ nickname. If the relay identifier could be either a fingerprint *or*
+ nickname then it's queried as a fingerprint.
+
+ :param str relay: fingerprint or nickname of the relay to be queried
+ :param object default: response if the query fails
+
+ :returns: :class:`~stem.descriptor.microdescriptor.Microdescriptor` for the given relay
+
+ :raises:
+ * :class:`stem.ControllerError` if unable to query the descriptor
+ * **ValueError** if **relay** doesn't conform with the pattern for being
+ a fingerprint or nickname
+
+ An exception is only raised if we weren't provided a default response.
+ """
+
+ try:
+ if stem.util.tor_tools.is_valid_fingerprint(relay):
+ query = "md/id/%s" % relay
+ elif stem.util.tor_tools.is_valid_nickname(relay):
+ query = "md/name/%s" % relay
+ else:
+ raise ValueError("'%s' isn't a valid fingerprint or nickname" % relay)
+
+ desc_content = self.get_info(query)
+ return stem.descriptor.microdescriptor.Microdescriptor(desc_content)
+ except Exception, exc:
+ if default == UNDEFINED:
+ raise exc
+ else:
+ return default
+
def get_server_descriptor(self, relay, default = UNDEFINED):
"""
Provides the server descriptor for the relay with the given fingerprint or
diff --git a/stem/descriptor/microdescriptor.py b/stem/descriptor/microdescriptor.py
index e06d254..dc818aa 100644
--- a/stem/descriptor/microdescriptor.py
+++ b/stem/descriptor/microdescriptor.py
@@ -224,3 +224,18 @@ class Microdescriptor(stem.descriptor.Descriptor):
if "onion-key" != entries.keys()[0]:
raise ValueError("Microdescriptor must start with a 'onion-key' entry")
+
+ def _compare(self, other, method):
+ if not isinstance(other, Microdescriptor):
+ return False
+
+ return method(str(self).strip(), str(other).strip())
+
+ def __eq__(self, other):
+ return self._compare(other, lambda s, o: s == o)
+
+ def __lt__(self, other):
+ return self._compare(other, lambda s, o: s < o)
+
+ def __le__(self, other):
+ return self._compare(other, lambda s, o: s <= o)
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 6d55b80..23966a9 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -795,6 +795,43 @@ class TestController(unittest.TestCase):
self.assertTrue(stem.util.connection.is_valid_ip_address(ip_addr))
+ def test_get_microdescriptor(self):
+ """
+ Basic checks for get_microdescriptor().
+ """
+
+ if test.runner.require_control(self):
+ return
+
+ with test.runner.get_runner().get_tor_controller() as controller:
+ # we should balk at invalid content
+ self.assertRaises(ValueError, controller.get_microdescriptor, None)
+ self.assertRaises(ValueError, controller.get_microdescriptor, "")
+ self.assertRaises(ValueError, controller.get_microdescriptor, 5)
+ self.assertRaises(ValueError, controller.get_microdescriptor, "z" * 30)
+
+ # try with a relay that doesn't exist
+ self.assertRaises(stem.ControllerError, controller.get_microdescriptor, "blargg")
+ self.assertRaises(stem.ControllerError, controller.get_microdescriptor, "5" * 40)
+
+ # microdescriptors exclude the fingerprint and nickname so checking the
+ # consensus to get the nickname and fingerprint of a relay
+
+ test_router_status_entry = None
+
+ for desc in controller.get_network_statuses():
+ if desc.nickname != "Unnamed":
+ test_router_status_entry = desc
+ break
+
+ if test_router_status_entry is None:
+ self.fail("Unable to find any relays without a nickname of 'Unnamed'")
+
+ md_by_fingerprint = controller.get_microdescriptor(test_router_status_entry.fingerprint)
+ md_by_nickname = controller.get_microdescriptor(test_router_status_entry.nickname)
+
+ self.assertEqual(md_by_fingerprint, md_by_nickname)
+
def test_get_server_descriptor(self):
"""
Compares get_server_descriptor() against our cached descriptors.