[tor-commits] [stem/master] Implementing a get_consensus() method

atagar at torproject.org atagar at torproject.org
Mon Jul 22 03:10:17 UTC 2013


commit 5514b2cfd7d313f429bfe1bb8a13c975f7c3413f
Author: Damian Johnson <atagar at torproject.org>
Date:   Thu Jul 18 09:59:30 2013 -0700

    Implementing a get_consensus() method
    
    Originally this was gonna be a get_network_status(fingerprint) method but
    evedently we can't request individual router status entries. Understandable
    since signatures are for the whole document but still a bit of a pity from an
    API perspective. Oh well.
---
 stem/descriptor/__init__.py     |    2 +-
 stem/descriptor/remote.py       |   60 +++++++++++++++++++++++++++++----------
 test/integ/descriptor/remote.py |   32 ++++++++++++++++++++-
 test/runner.py                  |    1 +
 4 files changed, 78 insertions(+), 17 deletions(-)

diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index a527329..82f846d 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -132,7 +132,7 @@ def parse_file(descriptor_file, descriptor_type = None, validate = True, documen
   :param bool validate: checks the validity of the descriptor's content if
     **True**, skips these checks otherwise
   :param stem.descriptor.__init__.DocumentHandler document_handler: method in
-    which to parse :class:`~stem.descriptor.networkstatus.NetworkStatusDocument`
+    which to parse the :class:`~stem.descriptor.networkstatus.NetworkStatusDocument`
 
   :returns: iterator for :class:`~stem.descriptor.__init__.Descriptor` instances in the file
 
diff --git a/stem/descriptor/remote.py b/stem/descriptor/remote.py
index a18d991..66e6d85 100644
--- a/stem/descriptor/remote.py
+++ b/stem/descriptor/remote.py
@@ -82,6 +82,8 @@ class Query(object):
   :var str resource: resource being fetched, such as '/tor/status-vote/current/consensus.z'
   :var str descriptor_type: type of descriptors being fetched, see
     :func:`~stem.descriptor.__init__.parse_file`
+  :param stem.descriptor.__init__.DocumentHandler document_handler: method in
+    which to parse the :class:`~stem.descriptor.networkstatus.NetworkStatusDocument`
 
   :var list endpoints: (address, dirport) tuples of the authority or mirror
     we're querying, this uses authorities if undefined
@@ -101,9 +103,10 @@ class Query(object):
     finished
   """
 
-  def __init__(self, resource, descriptor_type, endpoints = None, retries = 2, fall_back_to_authority = True, timeout = None, start = True):
+  def __init__(self, resource, descriptor_type, endpoints = None, retries = 2, fall_back_to_authority = True, timeout = None, start = True, document_handler = stem.descriptor.DocumentHandler.ENTRIES):
     self.resource = resource
     self.descriptor_type = descriptor_type
+    self.document_handler = document_handler
 
     self.endpoints = endpoints if endpoints else []
     self.retries = retries
@@ -220,7 +223,7 @@ class Query(object):
 
       response = io.BytesIO(response.read().strip())
 
-      self._results = stem.descriptor.parse_file(response, self.descriptor_type)
+      self._results = stem.descriptor.parse_file(response, self.descriptor_type, document_handler = self.document_handler)
       log.trace("Descriptors retrieved from '%s' in %0.2fs" % (self.download_url, self.runtime))
     except:
       exc = sys.exc_info()[1]
@@ -278,14 +281,14 @@ class DescriptorDownloader(object):
 
     resource = '/tor/server/all'
 
+    if isinstance(fingerprints, str):
+      fingerprints = [fingerprints]
+
     if fingerprints:
-      if isinstance(fingerprints, str):
-        resource = '/tor/server/fp/%s' % fingerprints
-      else:
-        if len(fingerprints) > MAX_BATCH_SIZE:
-          raise ValueError("Unable to request more than %i descriptors at a time by their fingerprints" % MAX_BATCH_SIZE)
+      if len(fingerprints) > MAX_BATCH_SIZE:
+        raise ValueError("Unable to request more than %i descriptors at a time by their fingerprints" % MAX_BATCH_SIZE)
 
-        resource = '/tor/server/fp/%s' % '+'.join(fingerprints)
+      resource = '/tor/server/fp/%s' % '+'.join(fingerprints)
 
     return self._query(resource, 'server-descriptor 1.0')
 
@@ -306,18 +309,44 @@ class DescriptorDownloader(object):
 
     resource = '/tor/extra/all'
 
+    if isinstance(fingerprints, str):
+      fingerprints = [fingerprints]
+
     if fingerprints:
-      if isinstance(fingerprints, str):
-        resource = '/tor/extra/fp/%s' % fingerprints
-      else:
-        if len(fingerprints) > MAX_BATCH_SIZE:
-          raise ValueError("Unable to request more than %i descriptors at a time by their fingerprints" % MAX_BATCH_SIZE)
+      if len(fingerprints) > MAX_BATCH_SIZE:
+        raise ValueError("Unable to request more than %i descriptors at a time by their fingerprints" % MAX_BATCH_SIZE)
 
-        resource = '/tor/extra/fp/%s' % '+'.join(fingerprints)
+      resource = '/tor/extra/fp/%s' % '+'.join(fingerprints)
 
     return self._query(resource, 'extra-info 1.0')
 
-  def _query(self, resource, descriptor_type):
+  def get_consensus(self, document_handler = stem.descriptor.DocumentHandler.ENTRIES, authority_v3ident = None):
+    """
+    Provides the present router status entries.
+
+    :param stem.descriptor.__init__.DocumentHandler document_handler: method in
+      which to parse the :class:`~stem.descriptor.networkstatus.NetworkStatusDocumentV3`
+    :param str authority_v3ident: fingerprint of the authority key for which
+      to get the consensus, see `'v3ident' in tor's config.c
+      <https://gitweb.torproject.org/tor.git/blob/f631b73:/src/or/config.c#l816>`_
+      for the values.
+
+    :returns: :class:`~stem.descriptor.remote.Query` for the router status
+      entries
+    """
+
+    resource = '/tor/status-vote/current/consensus'
+
+    if authority_v3ident:
+      resource += '/%s' % authority_v3ident
+
+    return self._query(
+      resource,
+      'network-status-consensus-3 1.0',
+      document_handler = document_handler,
+    )
+
+  def _query(self, resource, descriptor_type, document_handler = stem.descriptor.DocumentHandler.ENTRIES):
     """
     Issues a request for the given resource.
     """
@@ -330,4 +359,5 @@ class DescriptorDownloader(object):
       fall_back_to_authority = self.fall_back_to_authority,
       timeout = self.timeout,
       start = self.start_when_requested,
+      document_handler = document_handler,
     )
diff --git a/test/integ/descriptor/remote.py b/test/integ/descriptor/remote.py
index 62348ab..9273b61 100644
--- a/test/integ/descriptor/remote.py
+++ b/test/integ/descriptor/remote.py
@@ -4,9 +4,10 @@ Integration tests for stem.descriptor.remote.
 
 import unittest
 
-import stem.descriptor.server_descriptor
 import stem.descriptor.extrainfo_descriptor
 import stem.descriptor.remote
+import stem.descriptor.router_status_entry
+import stem.descriptor.server_descriptor
 import test.runner
 
 # Required to prevent unmarshal error when running this test alone.
@@ -26,6 +27,8 @@ class TestDescriptorReader(unittest.TestCase):
 
     if test.runner.require_online(self):
       return
+    elif test.runner.only_run_once(self, "test_using_authorities"):
+      return
 
     queries = []
 
@@ -51,6 +54,11 @@ class TestDescriptorReader(unittest.TestCase):
     Exercises the downloader's get_server_descriptors() method.
     """
 
+    if test.runner.require_online(self):
+      return
+    elif test.runner.only_run_once(self, "test_get_server_descriptors"):
+      return
+
     downloader = stem.descriptor.remote.DescriptorDownloader()
 
     # Fetch a single descriptor and a batch. I'd love to also exercise
@@ -82,6 +90,11 @@ class TestDescriptorReader(unittest.TestCase):
     Exercises the downloader's get_extrainfo_descriptors() method.
     """
 
+    if test.runner.require_online(self):
+      return
+    elif test.runner.only_run_once(self, "test_get_extrainfo_descriptors"):
+      return
+
     downloader = stem.descriptor.remote.DescriptorDownloader()
 
     single_query = downloader.get_extrainfo_descriptors('9695DFC35FFEB861329B9F1AB04C46397020CE31')
@@ -101,4 +114,21 @@ class TestDescriptorReader(unittest.TestCase):
 
     self.assertEqual(2, len(list(multiple_query)))
 
+  def test_get_consensus(self):
+    """
+    Exercises the downloader's get_consensus() method.
+    """
+
+    if test.runner.require_online(self):
+      return
+    elif test.runner.only_run_once(self, "test_get_consensus"):
+      return
+
+    downloader = stem.descriptor.remote.DescriptorDownloader()
+
+    consensus_query = downloader.get_consensus()
+    consensus_query.run()
 
+    consensus = list(consensus_query)
+    self.assertTrue(len(consensus) > 50)
+    self.assertTrue(isinstance(consensus[0], stem.descriptor.router_status_entry.RouterStatusEntryV3))
diff --git a/test/runner.py b/test/runner.py
index 9bd62f7..a1eda9e 100644
--- a/test/runner.py
+++ b/test/runner.py
@@ -16,6 +16,7 @@ about the tor test instance they're running against.
   require_control - skips the test unless tor provides a controller endpoint
   require_version - skips the test unless we meet a tor version requirement
   require_online - skips unless targets allow for online tests
+  only_run_once - skip the test if it has been ran before
   exercise_controller - basic sanity check that a controller connection can be used
 
   get_runner - Singleton for fetching our runtime context.





More information about the tor-commits mailing list