commit 5514b2cfd7d313f429bfe1bb8a13c975f7c3413f Author: Damian Johnson atagar@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.
tor-commits@lists.torproject.org