[tor-commits] [stem/master] Initial support for STATUS_* events

atagar at torproject.org atagar at torproject.org
Mon Dec 3 02:35:44 UTC 2012


commit 628dd1f3246566bf4d7b4b2e2932f459bec32986
Author: Damian Johnson <atagar at torproject.org>
Date:   Thu Nov 29 18:49:50 2012 -0800

    Initial support for STATUS_* events
    
    The STATUS_GENERAL, STATUS_CLIENT, and STATUS_SERVER events all follow a nice,
    well specified format. The specific action instances for these events warrant
    subclasses, but to start with adding the StatusEvent class which covers the
    basic attributes.
---
 stem/__init__.py             |   43 ++++++++++++++++++++++++++++
 stem/control.py              |   23 ++++++++++----
 stem/response/events.py      |   57 ++++++++++++++++++++++++++++++++----
 test/unit/response/events.py |   64 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 173 insertions(+), 14 deletions(-)

diff --git a/stem/__init__.py b/stem/__init__.py
index 68bda60..c0b47fd 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -14,6 +14,20 @@ Library for working with the tor process.
     +- SocketError - Communication with the socket failed.
        +- SocketClosed - Socket has been shut down.
 
+.. data:: Runlevel (enum)
+  
+  Rating of importance used for event logging.
+  
+  =========== ===========
+  Runlevel    Description
+  =========== ===========
+  **ERR**     critical issues that impair tor's ability to function
+  **WARN**    non-critical issues the user should be aware of
+  **NOTICE**  information that may be helpful to the user
+  **INFO**    high level runtime information
+  **DEBUG**   low level runtime information
+  =========== ===========
+
 .. data:: CircStatus (enum)
   
   Statuses that a circuit can be in. Tor may provide statuses not in this enum.
@@ -234,6 +248,18 @@ Library for working with the tor process.
   **DROPPED**           descriptor rejected without notifying the relay
   **REJECTED**          relay notified that its descriptor has been rejected
   ===================== ===========
+
+.. data:: StatusType (enum)
+  
+  Sources for tor status events. Tor may provide types not in this enum.
+  
+  ============= ===========
+  StatusType    Description
+  ============= ===========
+  **GENERAL**   general tor activity, not specifically as a client or relay
+  **CLIENT**    related to our activity as a tor client
+  **SERVER**    related to our activity as a tor relay
+  ============= ===========
 """
 
 __version__ = '0.0.1'
@@ -261,6 +287,7 @@ __all__ = [
   "InvalidArguments",
   "SocketError",
   "SocketClosed",
+  "Runlevel",
   "CircStatus",
   "CircBuildFlag",
   "CircPurpose",
@@ -272,6 +299,8 @@ __all__ = [
   "StreamPurpose",
   "ORStatus",
   "ORClosureReason",
+  "AuthDescriptorAction",
+  "StatusType",
 ]
 
 import stem.util.enum
@@ -326,6 +355,14 @@ class SocketError(ControllerError):
 class SocketClosed(SocketError):
   "Control socket was closed before completing the message."
 
+Runlevel = stem.util.enum.UppercaseEnum(
+  "DEBUG",
+  "INFO",
+  "NOTICE",
+  "WARN",
+  "ERR",
+)
+
 CircStatus = stem.util.enum.UppercaseEnum(
   "LAUNCHED",
   "BUILT",
@@ -453,3 +490,9 @@ AuthDescriptorAction = stem.util.enum.UppercaseEnum(
   "REJECTED",
 )
 
+StatusType = stem.util.enum.UppercaseEnum(
+  "GENERAL",
+  "CLIENT",
+  "SERVER",
+)
+
diff --git a/stem/control.py b/stem/control.py
index 29b77ef..45b7df8 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -66,25 +66,34 @@ providing its own for interacting at a higher level.
   
   Known types of events that the
   :func:`~stem.control.Controller.add_event_listener` method of the
-  :class:`~stem.control.Controller` can listen for. Enums are mapped to
-  :class:`~stem.response.events.Event` subclasses as follows...
+  :class:`~stem.control.Controller` can listen for.
+  
+  The most frequently listened for event types tend to be the logging events
+  (**DEBUG**, **INFO**, **NOTICE**, **WARN**, and **ERR**), bandwidth usage
+  (**BW**), and circuit or stream changes (**CIRC** and **STREAM**).
+  
+  Enums are mapped to :class:`~stem.response.events.Event` subclasses as
+  follows...
   
   ===================== ===========
   EventType             Event Class
   ===================== ===========
-  **DEBUG**             :class:`stem.response.events.LogEvent`
-  **INFO**              :class:`stem.response.events.LogEvent`
-  **NOTICE**            :class:`stem.response.events.LogEvent`
-  **WARN**              :class:`stem.response.events.LogEvent`
-  **ERR**               :class:`stem.response.events.LogEvent`
   **ADDRMAP**           :class:`stem.response.events.AddrMapEvent`
   **AUTHDIR_NEWDESCS**  :class:`stem.response.events.AuthDirNewDescEvent`
   **BW**                :class:`stem.response.events.BandwidthEvent`
   **CIRC**              :class:`stem.response.events.CircuitEvent`
+  **DEBUG**             :class:`stem.response.events.LogEvent`
   **DESCCHANGED**       :class:`stem.response.events.DescChangedEvent`
+  **ERR**               :class:`stem.response.events.LogEvent`
+  **INFO**              :class:`stem.response.events.LogEvent`
   **NEWDESC**           :class:`stem.response.events.NewDescEvent`
+  **NOTICE**            :class:`stem.response.events.LogEvent`
   **ORCONN**            :class:`stem.response.events.ORConnEvent`
+  **STATUS_CLIENT**     :class:`stem.response.events.StatusEvent`
+  **STATUS_GENERAL**    :class:`stem.response.events.StatusEvent`
+  **STATUS_SERVER**     :class:`stem.response.events.StatusEvent`
   **STREAM**            :class:`stem.response.events.StreamEvent`
+  **WARN**              :class:`stem.response.events.LogEvent`
   ===================== ===========
 """
 
diff --git a/stem/response/events.py b/stem/response/events.py
index e571c4e..ab0e5ef 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -304,9 +304,9 @@ class DescChangedEvent(Event):
 class LogEvent(Event):
   """
   Tor logging event. These are the most visible kind of event since, by
-  default, tor logs at the NOTICE runlevel to stdout.
+  default, tor logs at the NOTICE :data:`~stem.Runlevel` to stdout.
   
-  :var str runlevel: runlevel of the logged message
+  :var stem.Runlevel runlevel: runlevel of the logged message
   :var str message: logged message
   """
   
@@ -319,6 +319,14 @@ class LogEvent(Event):
     # multi-line message
     
     self.message = str(self)[len(self.runlevel) + 1:].rstrip("\nOK")
+    
+    # log if our runlevel isn't recognized
+    
+    unrecognized_msg = UNRECOGNIZED_ATTR_MSG % ("Logging", self)
+    
+    if not self.runlevel in stem.Runlevel:
+      log_id = "event.logging.unknown_runlevel.%s" % self.runlevel
+      log.log_once(log_id, log.INFO, unrecognized_msg % ('runlevel', self.runlevel))
 
 class NewDescEvent(Event):
   """
@@ -402,6 +410,38 @@ class ORConnEvent(Event):
       log_id = "event.orconn.unknown_reason.%s" % self.reason
       log.log_once(log_id, log.INFO, unrecognized_msg % ('reason', self.reason))
 
+class StatusEvent(Event):
+  """
+  Notification of a change in tor's state. These are generally triggered for
+  the same sort of things as log messages of the NOTICE level or higher.
+  However, unlike :class:`~stem.response.events.LogEvent` these contain well
+  formed data.
+  
+  :var stem.StatusType status_type: category of the status event
+  :var stem.Runlevel runlevel: runlevel of the logged message
+  :var str message: logged message
+  """
+  
+  _POSITIONAL_ARGS = ("runlevel", "action")
+  
+  def _parse(self):
+    if self.type == 'STATUS_GENERAL':
+      self.status_type = stem.StatusType.GENERAL
+    elif self.type == 'STATUS_CLIENT':
+      self.status_type = stem.StatusType.CLIENT
+    elif self.type == 'STATUS_SERVER':
+      self.status_type = stem.StatusType.SERVER
+    else:
+      raise ValueError("BUG: Unrecognized status type (%s), likely an EVENT_TYPE_TO_CLASS addition without revising how 'status_type' is assigned." % self.type)
+    
+    # log if our runlevel isn't recognized
+    
+    unrecognized_msg = UNRECOGNIZED_ATTR_MSG % ("Status", self)
+    
+    if not self.runlevel in stem.Runlevel:
+      log_id = "event.status.unknown_runlevel.%s" % self.runlevel
+      log.log_once(log_id, log.INFO, unrecognized_msg % ('runlevel', self.runlevel))
+
 class StreamEvent(Event):
   """
   Event that indicates that a stream has changed.
@@ -483,18 +523,21 @@ class StreamEvent(Event):
       log.log_once(log_id, log.INFO, unrecognized_msg % ('purpose', self.purpose))
 
 EVENT_TYPE_TO_CLASS = {
-  "DEBUG": LogEvent,
-  "INFO": LogEvent,
-  "NOTICE": LogEvent,
-  "WARN": LogEvent,
-  "ERR": LogEvent,
   "ADDRMAP": AddrMapEvent,
   "AUTHDIR_NEWDESCS": AuthDirNewDescEvent,
   "BW": BandwidthEvent,
   "CIRC": CircuitEvent,
+  "DEBUG": LogEvent,
   "DESCCHANGED": DescChangedEvent,
+  "ERR": LogEvent,
+  "INFO": LogEvent,
   "NEWDESC": NewDescEvent,
+  "NOTICE": LogEvent,
   "ORCONN": ORConnEvent,
+  "STATUS_CLIENT": StatusEvent,
+  "STATUS_GENERAL": StatusEvent,
+  "STATUS_SERVER": StatusEvent,
   "STREAM": StreamEvent,
+  "WARN": LogEvent,
 }
 
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index 0a5921f..41a9d0e 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -41,6 +41,61 @@ $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone"
 CIRC_BUILT_OLD = "650 CIRC 1 BUILT \
 $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14"
 
+# STATUS_* events that I was able to easily trigger. Most came from starting
+# TBB, then listening while it bootstrapped.
+
+STATUS_CLIENT_CONSENSUS_ARRIVED = "650 STATUS_CLIENT NOTICE CONSENSUS_ARRIVED"
+STATUS_CLIENT_ENOUGH_DIR_INFO = "650 STATUS_CLIENT NOTICE ENOUGH_DIR_INFO"
+STATUS_CLIENT_CIRC_ESTABLISHED = "650 STATUS_CLIENT NOTICE CIRCUIT_ESTABLISHED"
+
+STATUS_CLIENT_BOOTSTRAP_DESCRIPTORS = '650 STATUS_CLIENT NOTICE BOOTSTRAP \
+PROGRESS=53 \
+TAG=loading_descriptors \
+SUMMARY="Loading relay descriptors"'
+
+STATUS_CLIENT_BOOTSTRAP_STUCK = '650 STATUS_CLIENT WARN BOOTSTRAP \
+PROGRESS=80 \
+TAG=conn_or \
+SUMMARY="Connecting to the Tor network" \
+WARNING="Network is unreachable" \
+REASON=NOROUTE \
+COUNT=5 \
+RECOMMENDATION=warn'
+
+STATUS_CLIENT_BOOTSTRAP_CONNECTING = '650 STATUS_CLIENT NOTICE BOOTSTRAP \
+PROGRESS=80 \
+TAG=conn_or \
+SUMMARY="Connecting to the Tor network"'
+
+STATUS_CLIENT_BOOTSTRAP_FIRST_HANDSHAKE = '650 STATUS_CLIENT NOTICE BOOTSTRAP \
+PROGRESS=85 \
+TAG=handshake_or \
+SUMMARY="Finishing handshake with first hop"'
+
+STATUS_CLIENT_BOOTSTRAP_ESTABLISHED = '650 STATUS_CLIENT NOTICE BOOTSTRAP \
+PROGRESS=90 \
+TAG=circuit_create \
+SUMMARY="Establishing a Tor circuit"'
+
+STATUS_CLIENT_BOOTSTRAP_DONE = '650 STATUS_CLIENT NOTICE BOOTSTRAP \
+PROGRESS=100 \
+TAG=done \
+SUMMARY="Done"'
+
+STATUS_SERVER_CHECK_REACHABILITY = "650 STATUS_SERVER NOTICE CHECKING_REACHABILITY \
+ORADDRESS=71.35.143.230:9050"
+
+STATUS_SERVER_DNS_TIMEOUT = '650 STATUS_SERVER NOTICE NAMESERVER_STATUS \
+NS=205.171.3.25 \
+STATUS=DOWN \
+ERR="request timed out."'
+
+STATUS_SERVER_DNS_DOWN = "650 STATUS_SERVER WARN NAMESERVER_ALL_DOWN"
+
+STATUS_SERVER_DNS_UP = "650 STATUS_SERVER NOTICE NAMESERVER_STATUS \
+NS=205.171.3.25 \
+STATUS=UP"
+
 # STREAM events from tor 0.2.3.16 for visiting the google front page
 
 STREAM_NEW = "650 STREAM 18 NEW 0 \
@@ -319,6 +374,15 @@ class TestEvents(unittest.TestCase):
     self.assertEqual(ORClosureReason.DONE, event.reason)
     self.assertEqual(None, event.circ_count)
   
+  def test_status_event(self):
+    event = _get_event(STATUS_CLIENT_CONSENSUS_ARRIVED)
+    
+    self.assertTrue(isinstance(event, stem.response.events.StatusEvent))
+    self.assertEqual(STATUS_CLIENT_CONSENSUS_ARRIVED.lstrip("650 "), str(event))
+    self.assertEqual(StatusType.CLIENT, event.status_type)
+    self.assertEqual(Runlevel.NOTICE, event.runlevel)
+    self.assertEqual("CONSENSUS_ARRIVED", event.action)
+  
   def test_stream_event(self):
     event = _get_event(STREAM_NEW)
     





More information about the tor-commits mailing list