
commit d72c21de710ba78e16b6d0fa29949fb43a2e5eb6 Author: Damian Johnson <atagar@torproject.org> Date: Sat Mar 24 18:15:27 2012 -0700 Server descriptor test for metrics content Test for parsing a single server descriptor and checking its content. This includes a few fixes and improvements for the ServerDescriptorV2 class, the most intersting of which is that declaring instance variables prior to init makes a single reference. Hence all server descriptors were using the same exit policy. Oops. :) --- run_tests.py | 2 + stem/descriptor/server_descriptor.py | 111 +++++++++++++++++----------- test/integ/descriptor/__init__.py | 2 +- test/integ/descriptor/server_descriptor.py | 83 +++++++++++++++++++++ 4 files changed, 155 insertions(+), 43 deletions(-) diff --git a/run_tests.py b/run_tests.py index e0574d3..3cb259d 100755 --- a/run_tests.py +++ b/run_tests.py @@ -31,6 +31,7 @@ import test.integ.control.base_controller import test.integ.socket.control_message import test.integ.socket.control_socket import test.integ.descriptor.reader +import test.integ.descriptor.server_descriptor import test.integ.util.conf import test.integ.util.system import test.integ.version @@ -98,6 +99,7 @@ INTEG_TESTS = ( test.integ.util.conf.TestConf, test.integ.util.system.TestSystem, test.integ.descriptor.reader.TestDescriptorReader, + test.integ.descriptor.server_descriptor.TestServerDescriptor, test.integ.version.TestVersion, test.integ.socket.control_socket.TestControlSocket, test.integ.socket.control_message.TestControlMessage, diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index 70db1fe..7715a96 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -192,45 +192,33 @@ class ServerDescriptorV2(stem.descriptor.Descriptor): Attributes: nickname (str) - relay's nickname (*) + fingerprint (str) - fourty hex digits that make up the relay's fingerprint address (str) - IPv4 address of the relay (*) or_port (int) - port used for relaying (*) socks_port (int) - deprecated attribute, always zero (*) dir_port (int) - deprecated port used for descriptor mirroring (*) - average_bandwidth (int) - rate of traffic relay is willing to relay in bytes/s (*) - burst_bandwidth (int) - rate of traffic relay is willing to burst to in bytes/s (*) - observed_bandwidth (int) - estimated capacity of the relay based on usage in bytes/s (*) platform (str) - operating system and tor version tor_version (stem.version.Version) - version of tor - exit_policy (stem.exit_policy.ExitPolicy) - relay's stated exit policy + operating_system (str) - relay's operating system + uptime (int) - relay's uptime when published in seconds published (datetime.datetime) - time in GMT when the descriptor was generated (*) - fingerprint (str) - fourty hex digits that make up the relay's fingerprint + contact (str) - relay's contact information hibernating (bool) - flag to indicate if the relay was hibernating when published (*) - uptime (int) - relay's uptime when published in seconds + exit_policy (stem.exit_policy.ExitPolicy) - relay's stated exit policy + family (list) - nicknames or fingerprints of relays it has a declared family with (*) + average_bandwidth (int) - rate of traffic relay is willing to relay in bytes/s (*) + burst_bandwidth (int) - rate of traffic relay is willing to burst to in bytes/s (*) + observed_bandwidth (int) - estimated capacity of the relay based on usage in bytes/s (*) onion_key (str) - key used to encrypt EXTEND cells (*) onion_key_type (str) - block type of the onion_key, probably "RSA PUBLIC KEY" (*) signing_key (str) - relay's long-term identity key (*) signing_key_type (str) - block type of the signing_key, probably "RSA PUBLIC KEY" (*) - router_sig (str) - signature for this descriptor (*) - router_sig_type (str) - block type of the router_sig, probably "SIGNATURE" (*) - contact (str) - relay's contact information - family (list) - nicknames or fingerprints of relays it has a declared family with (*) + signature (str) - signature for this descriptor (*) + signature_type (str) - block type of the signature, probably "SIGNATURE" (*) (*) required fields, others are left as None if undefined """ - nickname = address = or_port = socks_port = dir_port = None - average_bandwidth = burst_bandwidth = observed_bandwidth = None - platform = tor_version = published = fingerprint = uptime = None - onion_key = onion_key_type = signing_key = signing_key_type = None - router_sig = router_sig_type = contact = None - hibernating = False - family = unrecognized_lines = [] - - # TODO: Until we have a proper ExitPolicy class this is just a list of the - # exit policy strings... - - exit_policy = [] - def __init__(self, contents, validate = True, annotations = None): """ Version 2 server descriptor constructor, created from an individual relay's @@ -253,14 +241,49 @@ class ServerDescriptorV2(stem.descriptor.Descriptor): stem.descriptor.Descriptor.__init__(self, contents) - self._annotation_lines = annotations - self._annotation_dict = {} + self.nickname = None + self.fingerprint = None + self.address = None + self.or_port = None + self.socks_port = None + self.dir_port = None + self.platform = None + self.tor_version = None + self.operating_system = None + self.uptime = None + self.published = None + self.contact = None + self.hibernating = False + self.family = [] + self.average_bandwidth = None + self.burst_bandwidth = None + self.observed_bandwidth = None + self.onion_key = None + self.onion_key_type = None + self.signing_key = None + self.signing_key_type = None + self.signature = None + self.signature_type = None + + # TODO: Until we have a proper ExitPolicy class this is just a list of the + # exit policy strings... - for line in annotations: - if " " in line: - key, value = line.split(" ", 1) - self._annotation_dict[key] = value - else: self._annotation_dict[line] = None + self.exit_policy = [] + + self._unrecognized_lines = [] + + if annotations: + self._annotation_lines = annotations + self._annotation_dict = {} + + for line in annotations: + if " " in line: + key, value = line.split(" ", 1) + self._annotation_dict[key] = value + else: self._annotation_dict[line] = None + else: + self._annotation_lines = [] + self._annotation_dict = {} # A descriptor contains a series of 'keyword lines' which are simply a # keyword followed by an optional value. Lines can also be followed by a @@ -271,7 +294,6 @@ class ServerDescriptorV2(stem.descriptor.Descriptor): # does not matter so breaking it into key / value pairs. entries = {} - remaining_contents = contents.split("\n") while remaining_contents: line = remaining_contents.pop(0) @@ -343,12 +365,14 @@ class ServerDescriptorV2(stem.descriptor.Descriptor): raise ValueError("Router line's SocksPort should be zero: %s" % router_comp[3]) elif not stem.util.connection.is_valid_port(router_comp[4], allow_zero = True): raise ValueError("Router line's DirPort is invalid: %s" % router_comp[4]) + elif not (router_comp[2].isdigit() and router_comp[3].isdigit() and router_comp[4].isdigit()): + continue self.nickname = router_comp[0] self.address = router_comp[1] - self.or_port = router_comp[2] - self.socks_port = router_comp[3] - self.dir_port = router_comp[4] + self.or_port = int(router_comp[2]) + self.socks_port = int(router_comp[3]) + self.dir_port = int(router_comp[4]) elif keyword == "bandwidth": # "bandwidth" bandwidth-avg bandwidth-burst bandwidth-observed bandwidth_comp = value.split() @@ -379,13 +403,16 @@ class ServerDescriptorV2(stem.descriptor.Descriptor): # version followed by the os like the following... # platform Tor 0.2.2.35 (git-73ff13ab3cc9570d) on Linux x86_64 # - # There's no guerentee that we'll be able to pick out the version. + # There's no guerentee that we'll be able to pick these out the + # version, but might as well try to save our caller the effot. - platform_comp = self.platform.split() + platform_match = re.match("^Tor (\S*).* on (.*)$", self.platform) - if platform_comp[0] == "Tor" and len(platform_comp) >= 2: + if platform_match: + version_str, self.operating_system = platform_match.groups() + try: - self.tor_version = stem.version.Version(platform_comp[1]) + self.tor_version = stem.version.Version(version_str) except ValueError: pass elif keyword == "published": # "published" YYYY-MM-DD HH:MM:SS @@ -439,17 +466,17 @@ class ServerDescriptorV2(stem.descriptor.Descriptor): if validate and (not block_type or not block_contents): raise ValueError("Router signature line must be followed by a signature block: %s" % line) - self.router_sig_type = block_type - self.router_sig = block_contents + self.signature_type = block_type + self.signature = block_contents elif keyword == "contact": self.contact = value elif keyword == "family": self.family = value.split(" ") else: - self.unrecognized_lines.append(line) + self._unrecognized_lines.append(line) def get_unrecognized_lines(self): - return list(unrecognized_lines) + return list(self._unrecognized_lines) def get_annotations(self): """ diff --git a/test/integ/descriptor/__init__.py b/test/integ/descriptor/__init__.py index e1095ca..b143c2a 100644 --- a/test/integ/descriptor/__init__.py +++ b/test/integ/descriptor/__init__.py @@ -2,5 +2,5 @@ Integration tests for stem.descriptor.* contents. """ -__all__ = ["reader"] +__all__ = ["reader", "server_descriptor"] diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py new file mode 100644 index 0000000..34b77a4 --- /dev/null +++ b/test/integ/descriptor/server_descriptor.py @@ -0,0 +1,83 @@ +""" +Integration tests for stem.descriptor.server_descriptor. +""" + +import os +import datetime +import unittest + +import stem.version +import stem.descriptor.server_descriptor + +my_dir = os.path.dirname(__file__) +DESCRIPTOR_TEST_DATA = os.path.join(my_dir, "data") + +class TestServerDescriptor(unittest.TestCase): + def test_metrics_descriptor(self): + """ + Parses and checks our results against a server descriptor from metrics. + """ + + descriptor_path = os.path.join(DESCRIPTOR_TEST_DATA, "example_descriptor") + + descriptor_file = open(descriptor_path) + descriptor_contents = descriptor_file.read() + descriptor_file.close() + + expected_published = datetime.datetime(2012, 3, 1, 17, 15, 27) + + expected_family = [ + "$0CE3CFB1E9CC47B63EA8869813BF6FAB7D4540C1", + "$1FD187E8F69A9B74C9202DC16A25B9E7744AB9F6", + "$74FB5EFA6A46DE4060431D515DC9A790E6AD9A7C", + "$77001D8DA9BF445B0F81AA427A675F570D222E6A", + "$B6D83EC2D9E18B0A7A33428F8CFA9C536769E209", + "$D2F37F46182C23AB747787FD657E680B34EAF892", + "$E0BD57A11F00041A9789577C53A1B784473669E4", + "$E5E3E9A472EAF7BE9682B86E92305DB4C71048EF", + ] + + expected_onion_key = """-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAJv5IIWQ+WDWYUdyA/0L8qbIkEVH/cwryZWoIaPAzINfrw1WfNZGtBmg +skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+ +WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE= +-----END RSA PUBLIC KEY-----""" + + expected_signing_key = """-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAKwvOXyztVKnuYvpTKt+nS3XIKeO8dVungi8qGoeS+6gkR6lDtGfBTjd +uE9UIkdAl9zi8/1Ic2wsUNHE9jiS0VgeupITGZY8YOyMJJ/xtV1cqgiWhq1dUYaq +51TOtUogtAPgXPh4J+V8HbFFIcCzIh3qCO/xXo+DSHhv7SSif1VpAgMBAAE= +-----END RSA PUBLIC KEY-----""" + + expected_signature = """-----BEGIN SIGNATURE----- +dskLSPz8beUW7bzwDjR6EVNGpyoZde83Ejvau+5F2c6cGnlu91fiZN3suE88iE6e +758b9ldq5eh5mapb8vuuV3uO+0Xsud7IEOqfxdkmk0GKnUX8ouru7DSIUzUL0zqq +Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= +-----END SIGNATURE-----""" + + desc = stem.descriptor.server_descriptor.ServerDescriptorV2(descriptor_contents) + self.assertEquals("caerSidi", desc.nickname) + self.assertEquals("A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB", desc.fingerprint) + self.assertEquals("71.35.133.197", desc.address) + self.assertEquals(9001, desc.or_port) + self.assertEquals(0, desc.socks_port) + self.assertEquals(0, desc.dir_port) + self.assertEquals("Tor 0.2.1.30 on Linux x86_64", desc.platform) + self.assertEquals(stem.version.Version("0.2.1.30"), desc.tor_version) + self.assertEquals("Linux x86_64", desc.operating_system) + self.assertEquals(588217, desc.uptime) + self.assertEquals(expected_published, desc.published) + self.assertEquals("www.atagar.com/contact", desc.contact) + self.assertEquals(False, desc.hibernating) + self.assertEquals(expected_family, desc.family) + self.assertEquals(153600, desc.average_bandwidth) + self.assertEquals(256000, desc.burst_bandwidth) + self.assertEquals(104590, desc.observed_bandwidth) + self.assertEquals(["reject *:*"], desc.exit_policy) + self.assertEquals(expected_onion_key, desc.onion_key) + self.assertEquals("RSA PUBLIC KEY", desc.onion_key_type) + self.assertEquals(expected_signing_key, desc.signing_key) + self.assertEquals("RSA PUBLIC KEY", desc.signing_key_type) + self.assertEquals(expected_signature, desc.signature) + self.assertEquals("SIGNATURE", desc.signature_type) +