commit 628dd1f3246566bf4d7b4b2e2932f459bec32986
Author: Damian Johnson <atagar(a)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)