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