[tor-commits] [stem/master] Adding a tutorial to our sphinx front page

atagar at torproject.org atagar at torproject.org
Sun Oct 14 02:12:16 UTC 2012


commit 23513eb220ed195ee73fcadd6fb826c3581810fc
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Oct 13 19:09:06 2012 -0700

    Adding a tutorial to our sphinx front page
    
    Tutorial for basic stem usage, with tests for the examples we provide. Our
    documentation is still incredibly beginner unfriendly, but at least this gives
    them a place to start.
    
    On a side note one of our integ tests kinda sorta killed our test instance by
    calling...
    
    controller.signal("INT")
    
    We didn't notice this because it happened in our very last test.
---
 docs/index.rst                             |  125 ++++++++++++++++++++++++++--
 test/integ/control/controller.py           |   28 ++++++-
 test/integ/descriptor/server_descriptor.py |   29 +++++++
 3 files changed, 173 insertions(+), 9 deletions(-)

diff --git a/docs/index.rst b/docs/index.rst
index 32b3d71..197977d 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,13 +1,126 @@
-.. Stem documentation master file, created by
-   sphinx-quickstart on Thu May 31 09:56:13 2012.
-   You can adapt this file completely to your liking, but it should at least
-   contain the root `toctree` directive.
-
 Welcome to Stem!
 ================
 
 Stem is a python controller library for `Tor <https://www.torproject.org/>`_. Like its predecessor, `TorCtl <https://www.torproject.org/getinvolved/volunteer.html.en#project-torctl>`_, it uses Tor's `control protocol <https://gitweb.torproject.org/torspec.git/blob/HEAD:/control-spec.txt>`_ to help developers program against the Tor process, enabling them to build things similar to `Vidalia <https://www.torproject.org/getinvolved/volunteer.html.en#project-vidalia>`_ and `arm <http://www.atagar.com/arm/>`_.
 
+Getting started with any new library can be rather daunting, so lets get our feet wet by jumping straight in with a tutorial...
+
+The Little Relay that Could
+---------------------------
+
+Lets say you just set up your very first `Tor relay <https://www.torproject.org/docs/tor-doc-relay.html.en>`_. Thank you! Now you want to write a script that tells you how much it is being used.
+
+First, for any script we write to be able to talk with our relay it'll need to have a control port available. This is a port that's usually only available on localhost and protected by either a password or authentication cookie.
+
+Look at your `torrc <https://www.torproject.org/docs/faq.html.en#torrc>`_ for the following configuration options...
+
+::
+
+  # This provides a port for the script we write to talk to. If you set this
+  # then be sure to also have either set the CookieAuthentication flag *or*
+  # provide a HashedControlPassword!
+  
+  ControlPort 9051
+  
+  # This will make Tor write an authentication cookie file. Anything that can
+  # read that file can connect to Tor. If you're going to run this script with
+  # the same user as Tor then this is the easiest method of authentication to
+  # use.
+  
+  CookieAuthentication 1
+  
+  # Alternatively we can authenticate with a password. To set a password first
+  # get its hash...
+  #
+  # % tor --hash-password "my_password"
+  # 16:E600ADC1B52C80BB6022A0E999A7734571A451EB6AE50FED489B72E3DF
+  #
+  # ... and use that for the HashedControlPassword in our torrc.
+  
+  HashedControlPassword 16:E600ADC1B52C80BB6022A0E999A7734571A451EB6AE50FED489B72E3DF
+
+You'll need to restart Tor or issue a SIGHUP for these new settings to take effect. Now lets write a script that tells us how many bytes Tor has sent and received...
+
+::
+
+  from stem.control import Controller
+  
+  controller = Controller.from_port(control_port = 9051)
+  controller.authenticate() # provide the password here if you set one
+  
+  bytes_read = controller.get_info("traffic/read")
+  bytes_written = controller.get_info("traffic/written")
+  
+  print "My Tor relay has read %s bytes and written %s." % (bytes_read, bytes_written)
+
+::
+
+  % python example.py 
+  My Tor relay has read 33406 bytes and written 29649.
+
+Congratulations! You've written your first controller script.
+
+Mirror Mirror on the Wall
+-------------------------
+
+A script that tells us our contributed bandwidth is neat and all, but now lets figure out who the *biggest* exit relays are.
+
+Information about the Tor relay network come from documents called **descriptors**. Descriptors can come from a few things...
+
+1. The Tor control port with GETINFO options like **desc/all-recent** and **ns/all**.
+2. Files in Tor's data directory, like **cached-descriptors** and **cached-consensus**.
+3. The descriptor archive on `Tor's metrics site <https://metrics.torproject.org/data.html>`_.
+
+We've already used the control port, so for this example we'll use the cached files directly. First locate Tor's data directory. If your torrc has a DataDirectory line then that's the spot. If not then check Tor's man page for the default location.
+
+Tor has several descriptor types. For bandwidth information we'll go to the server descriptors, which are located in the **cached-descriptors** file. These have somewhat infrequently changing information published by the relays themselves.
+
+To read this file we'll use the :class:`~stem.descriptor.reader.DescriptorReader`, a class designed to read descriptor files.
+
+::
+
+  import sys
+  from stem.descriptor.reader import DescriptorReader
+  
+  bw_to_relay = {} # mapping of observed bandwidth to the relay nicknames
+  
+  with DescriptorReader(["/home/atagar/.tor/cached-descriptors"]) as reader:
+    for desc in reader:
+      if desc.exit_policy.is_exiting_allowed():
+        bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname)
+  
+  sorted_bw = reversed(sorted(bw_to_relay.keys()))
+  
+  # prints the top fifteen relays
+  
+  count = 1
+  for bw_value in sorted_bw:
+    for nickname in bw_to_relay[bw_value]:
+      print "%i. %s (%i bytes/s)" % (count, nickname, bw_value)
+      count += 1
+      
+      if count > 15:
+        sys.exit()
+
+::
+
+  % python example.py 
+  1. herngaard (42939655 bytes/s)
+  2. chaoscomputerclub19 (42402911 bytes/s)
+  3. chaoscomputerclub18 (41967097 bytes/s)
+  4. chaoscomputerclub20 (40882989 bytes/s)
+  5. wannabe (40514411 bytes/s)
+  6. dorrisdeebrown (40349829 bytes/s)
+  7. manning2 (40057719 bytes/s)
+  8. chaoscomputerclub21 (38701399 bytes/s)
+  9. TorLand1 (37983627 bytes/s)
+  10. bolobolo1 (37676580 bytes/s)
+  11. manning1 (37117034 bytes/s)
+  12. gorz (35760527 bytes/s)
+  13. ndnr1 (26595129 bytes/s)
+  14. politkovskaja2 (26149682 bytes/s)
+  15. wau (25929953 bytes/s)
+
 :mod:`stem.connection`
 ----------------------
 
@@ -16,7 +129,7 @@ Connecting and authenticating to a Tor process.
 :mod:`stem.control`
 ----------------------
 
-Provides the :class:`stem.control.Controller` class which, as the name implies, is used for talking with and controlling a Tor instance. As a user this is the primary class that you'll need.
+Provides the :class:`~stem.control.Controller` class which, as the name implies, is used for talking with and controlling a Tor instance. As a user this is the primary class that you'll need.
 
 :mod:`stem.socket`
 ------------------
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 7c6708f..36ac14a 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -18,6 +18,31 @@ import test.runner
 import test.util
 
 class TestController(unittest.TestCase):
+  def test_tutorial(self):
+    """
+    Tests the tutorial from our front page.
+    """
+    
+    if test.runner.require_control(self): return
+    if test.runner.require_version(self, stem.version.Version("0.2.3.1")): return # uses a relatively new feature
+    elif not test.runner.Torrc.PORT in test.runner.get_runner().get_options():
+      test.runner.skip(self, "no port")
+      return
+    
+    from stem.control import Controller
+    
+    controller = Controller.from_port(control_port = test.runner.CONTROL_PORT)
+    
+    try:
+      controller.authenticate(test.runner.CONTROL_PASSWORD)
+      
+      bytes_read = controller.get_info("traffic/read")
+      bytes_written = controller.get_info("traffic/written")
+      
+      printed_msg = "My Tor relay has read %s bytes and written %s." % (bytes_read, bytes_written)
+    finally:
+      controller.close()
+  
   def test_from_port(self):
     """
     Basic sanity check for the from_port constructor.
@@ -362,9 +387,6 @@ class TestController(unittest.TestCase):
       
       # invalid signals
       self.assertRaises(stem.socket.InvalidArguments, controller.signal, "FOOBAR")
-      
-      controller.signal("INT")
-      self.assertRaises(stem.socket.SocketClosed, controller.msg, "GETINFO version")
   
   def test_extendcircuit(self):
     if test.runner.require_control(self): return
diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py
index 55e6545..21d0b33 100644
--- a/test/integ/descriptor/server_descriptor.py
+++ b/test/integ/descriptor/server_descriptor.py
@@ -16,6 +16,35 @@ import test.runner
 import test.integ.descriptor
 
 class TestServerDescriptor(unittest.TestCase):
+  def test_tutorial(self):
+    """
+    Runs the tutorial example for parsing server descriptors. We use our
+    metrics consensus rather than the cached one so it won't take overly long.
+    """
+    
+    from stem.descriptor.reader import DescriptorReader
+    
+    bw_to_relay = {} # mapping of observed bandwidth to the relay nicknames
+    
+    descriptor_path = test.integ.descriptor.get_resource("example_descriptor")
+    with DescriptorReader([descriptor_path]) as reader:
+      for desc in reader:
+        if desc.exit_policy.is_exiting_allowed():
+          bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname)
+    
+    sorted_bw = reversed(sorted(bw_to_relay.keys()))
+    
+    # prints the top fifteen relays
+    
+    count = 1
+    for bw_value in sorted_bw:
+      for nickname in bw_to_relay[bw_value]:
+        printed_line = "%i. %s (%i bytes/s)" % (count, nickname, bw_value)
+        count += 1
+        
+        if count > 15:
+          return
+  
   def test_metrics_descriptor(self):
     """
     Parses and checks our results against a server descriptor from metrics.



More information about the tor-commits mailing list