add IConnectionStatus and implementation

also a function to build one from a foolscap.Reconnector
This commit is contained in:
Brian Warner 2016-12-08 15:09:51 -08:00
parent 8d008967e7
commit 48fc14bd30
2 changed files with 138 additions and 0 deletions

View File

@ -2830,3 +2830,60 @@ class InsufficientVersionError(Exception):
class EmptyPathnameComponentError(Exception):
"""The webapi disallows empty pathname components."""
class IConnectionStatus(Interface):
"""
I hold information about the 'connectedness' for some reference.
Connections are an illusion, of course: only messages hold any meaning,
and they are fleeting. But for status displays, it is useful to pretend
that 'recently contacted' means a connection is established, and
'recently failed' means it is not.
This object is not 'live': it is created and populated when requested
from the connection manager, and it does not change after that point.
"""
connected = Attribute(
"""
Returns True if we appear to be connected: we've been successful
in communicating with our target at some point in the past, and we
haven't experienced any errors since then.""")
last_connection_time = Attribute(
"""
If is_connected() is True, this returns a number
(seconds-since-epoch) when we last transitioned from 'not connected'
to 'connected', such as when a TCP connect() operation completed and
subsequent negotiation was successful. Otherwise it returns None.
""")
last_connection_summary = Attribute(
"""
Returns a string with a brief summary of the current status, suitable
for display on an informational page. The more complete text from
last_connection_description would be appropriate for a tool-tip
popup.
""")
last_connection_description = Attribute(
"""
Returns a string with a description of the results of the most recent
connection attempt. For Foolscap connections, this indicates the
winning hint and the connection handler which used it, e.g.
'tcp:HOST:PORT via tcp' or 'tor:HOST.onion:PORT via tor':
* 'Connection successful: HINT via HANDLER (other hints: ..)'
* 'Connection failed: HINT->HANDLER->FAILURE, ...'
Note that this describes the last *completed* connection attempt. If
a connection attempt is currently in progress, this method will
describe the results of the previous attempt.
""")
last_received_time = Attribute(
"""
Returns a number (seconds-since-epoch) describing the last time we
heard anything (including low-level keep-alives or inbound requests)
from the other side.
""")

View File

@ -0,0 +1,81 @@
import time
from zope.interface import implementer
from ..interfaces import IConnectionStatus
@implementer(IConnectionStatus)
class ConnectionStatus:
def __init__(self, connected, summary,
last_connection_description, last_connection_time,
last_received_time):
self.connected = connected
self.last_connection_summary = summary
self.last_connection_description = last_connection_description
self.last_connection_time = last_connection_time
self.last_received_time = last_received_time
def _describe_statuses(hints, handlers, statuses):
descriptions = []
for hint in sorted(hints):
handler = handlers.get(hint)
handler_dsc = " via %s" % handler if handler else ""
status = statuses[hint]
descriptions.append(" %s%s: %s\n" % (hint, handler_dsc, status))
return "".join(descriptions)
def from_foolscap_reconnector(rc, last_received):
ri = rc.getReconnectionInfo()
state = ri.state
# the Reconnector shouldn't even be exposed until it is started, so we
# should never see "unstarted"
assert state in ("connected", "connecting", "waiting"), state
ci = ri.connectionInfo
if state == "connected":
connected = True
# build a description that shows the winning hint, and the outcomes
# of the losing ones
statuses = ci.connectorStatuses
handlers = ci.connectionHandlers
others = set(statuses.keys())
winner = ci.winningHint
if winner:
others.remove(winner)
winning_handler = ci.connectionHandlers[winner]
winning_dsc = "to %s via %s" % (winner, winning_handler)
else:
winning_dsc = "via listener (%s)" % ci.listenerStatus[0]
if others:
other_dsc = "\nother hints:\n%s" % \
_describe_statuses(others, handlers, statuses)
else:
other_dsc = ""
details = "Connection successful " + winning_dsc + other_dsc
summary = "Connected %s" % winning_dsc
last_connected = ci.establishedAt
elif state == "connecting":
connected = False
# ci describes the current in-progress attempt
statuses = ci.connectorStatuses
current = _describe_statuses(sorted(statuses.keys()),
ci.connectionHandlers, statuses)
details = "Trying to connect:\n%s" % current
summary = "Trying to connect"
last_connected = None
elif state == "waiting":
connected = False
now = time.time()
elapsed = now - ri.lastAttempt
delay = ri.nextAttempt - now
# ci describes the previous (failed) attempt
statuses = ci.connectorStatuses
last = _describe_statuses(sorted(statuses.keys()),
ci.connectionHandlers, statuses)
details = "Reconnecting in %d seconds\nLast attempt %ds ago:\n%s" \
% (delay, elapsed, last)
summary = "Reconnecting in %d seconds" % delay
last_connected = None
cs = ConnectionStatus(connected, summary, details,
last_connected, last_received)
return cs