mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-19 21:17:54 +00:00
Merge branch '2490-connection-info'
This updates the WUI welcome page with more information about each server connection (and the introducer connection): which handler is being used, how the connection process is going, and/or why it failed. Closes ticket:2819
This commit is contained in:
commit
fe1df17d65
4
setup.py
4
setup.py
@ -272,7 +272,7 @@ setup(name="tahoe-lafs", # also set in __init__.py
|
||||
"coverage",
|
||||
"mock",
|
||||
"tox",
|
||||
"foolscap[tor] >= 0.12.3",
|
||||
"foolscap[tor] >= 0.12.5",
|
||||
"txtorcon >= 0.17.0", # in case pip's resolver doesn't work
|
||||
"foolscap[i2p]",
|
||||
"txi2p >= 0.3.1", # in case pip's resolver doesn't work
|
||||
@ -280,7 +280,7 @@ setup(name="tahoe-lafs", # also set in __init__.py
|
||||
"pytest-twisted",
|
||||
],
|
||||
"tor": [
|
||||
"foolscap[tor] >= 0.12.3",
|
||||
"foolscap[tor] >= 0.12.5",
|
||||
"txtorcon >= 0.17.0", # in case pip's resolver doesn't work
|
||||
],
|
||||
"i2p": [
|
||||
|
@ -41,7 +41,8 @@ install_requires = [
|
||||
# with a FIPS build of OpenSSL.
|
||||
# * foolscap >= 0.12.3 provides tcp/tor/i2p connection handlers we need,
|
||||
# and allocate_tcp_port
|
||||
"foolscap >= 0.12.3",
|
||||
# * foolscap >= 0.12.5 has ConnectionInfo and ReconnectionInfo
|
||||
"foolscap >= 0.12.5",
|
||||
|
||||
# Needed for SFTP.
|
||||
# pycrypto 2.2 doesn't work due to <https://bugs.launchpad.net/pycrypto/+bug/620253>
|
||||
|
@ -610,7 +610,7 @@ class Client(node.Node, pollmixin.PollMixin):
|
||||
return self.encoding_params
|
||||
|
||||
def introducer_connection_statuses(self):
|
||||
return [ic.connected_to_introducer() for ic in self.introducer_clients]
|
||||
return [ic.connection_status() for ic in self.introducer_clients]
|
||||
|
||||
def connected_to_introducer(self):
|
||||
return any([ic.connected_to_introducer() for ic in self.introducer_clients])
|
||||
|
@ -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.
|
||||
""")
|
||||
|
||||
|
@ -8,7 +8,7 @@ from allmydata.introducer.interfaces import IIntroducerClient, \
|
||||
RIIntroducerSubscriberClient_v2
|
||||
from allmydata.introducer.common import sign_to_foolscap, unsign_from_foolscap,\
|
||||
get_tubid_string_from_ann
|
||||
from allmydata.util import log, yamlutil
|
||||
from allmydata.util import log, yamlutil, connection_status
|
||||
from allmydata.util.rrefutil import add_version_to_remote_reference
|
||||
from allmydata.util.keyutil import BadSignatureError
|
||||
from allmydata.util.assertutil import precondition
|
||||
@ -326,14 +326,16 @@ class IntroducerClient(service.Service, Referenceable):
|
||||
if service_name2 == service_name:
|
||||
eventually(cb, key_s, ann, *args, **kwargs)
|
||||
|
||||
def connection_status(self):
|
||||
assert self.running # startService builds _introducer_reconnector
|
||||
irc = self._introducer_reconnector
|
||||
last_received = (self._publisher.getDataLastReceivedAt()
|
||||
if self._publisher
|
||||
else None)
|
||||
return connection_status.from_foolscap_reconnector(irc, last_received)
|
||||
|
||||
def connected_to_introducer(self):
|
||||
return bool(self._publisher)
|
||||
|
||||
def get_since(self):
|
||||
return self._since
|
||||
|
||||
def get_last_received_data_time(self):
|
||||
if self._publisher is None:
|
||||
return None
|
||||
else:
|
||||
return self._publisher.getDataLastReceivedAt()
|
||||
|
@ -36,7 +36,7 @@ from twisted.application import service
|
||||
|
||||
from foolscap.api import eventually
|
||||
from allmydata.interfaces import IStorageBroker, IDisplayableServer, IServer
|
||||
from allmydata.util import log, base32
|
||||
from allmydata.util import log, base32, connection_status
|
||||
from allmydata.util.assertutil import precondition
|
||||
from allmydata.util.observer import ObserverList
|
||||
from allmydata.util.rrefutil import add_version_to_remote_reference
|
||||
@ -364,17 +364,16 @@ class NativeStorageServer(service.MultiService):
|
||||
return self.announcement
|
||||
def get_remote_host(self):
|
||||
return self.remote_host
|
||||
|
||||
def get_connection_status(self):
|
||||
last_received = None
|
||||
if self.rref:
|
||||
last_received = self.rref.getDataLastReceivedAt()
|
||||
return connection_status.from_foolscap_reconnector(self._reconnector,
|
||||
last_received)
|
||||
|
||||
def is_connected(self):
|
||||
return self._is_connected
|
||||
def get_last_connect_time(self):
|
||||
return self.last_connect_time
|
||||
def get_last_loss_time(self):
|
||||
return self.last_loss_time
|
||||
def get_last_received_data_time(self):
|
||||
if self.rref is None:
|
||||
return None
|
||||
else:
|
||||
return self.rref.getDataLastReceivedAt()
|
||||
|
||||
def get_available_space(self):
|
||||
version = self.get_version()
|
||||
|
@ -62,7 +62,8 @@ class Tor(unittest.TestCase):
|
||||
n = FakeNode(config)
|
||||
h = n._make_tor_handler()
|
||||
private_dir = os.path.join(n.basedir, "private")
|
||||
exp = mock.call(n._tor_provider._make_control_endpoint)
|
||||
exp = mock.call(n._tor_provider._make_control_endpoint,
|
||||
takes_status=True)
|
||||
self.assertEqual(f.mock_calls, [exp])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
@ -75,7 +76,9 @@ class Tor(unittest.TestCase):
|
||||
cfs = mock.Mock(return_value=tcep)
|
||||
with mock.patch("allmydata.util.tor_provider._launch_tor", launch_tor):
|
||||
with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
|
||||
cep = self.successResultOf(tp._make_control_endpoint(reactor))
|
||||
d = tp._make_control_endpoint(reactor,
|
||||
update_status=lambda status: None)
|
||||
cep = self.successResultOf(d)
|
||||
launch_tor.assert_called_with(reactor, executable, private_dir,
|
||||
tp._txtorcon)
|
||||
cfs.assert_called_with(reactor, "ep_desc")
|
||||
|
@ -271,6 +271,14 @@ class FakeConfig(dict):
|
||||
raise KeyError
|
||||
return value
|
||||
|
||||
class EmptyContext(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
def __enter__(self):
|
||||
pass
|
||||
def __exit__(self, type, value, traceback):
|
||||
pass
|
||||
|
||||
class Provider(unittest.TestCase):
|
||||
def test_build(self):
|
||||
tor_provider.Provider("basedir", FakeConfig(), "reactor")
|
||||
@ -298,13 +306,15 @@ class Provider(unittest.TestCase):
|
||||
txtorcon = mock.Mock()
|
||||
handler = object()
|
||||
tor.control_endpoint_maker = mock.Mock(return_value=handler)
|
||||
tor.add_context = mock.Mock(return_value=EmptyContext())
|
||||
with mock_tor(tor):
|
||||
with mock_txtorcon(txtorcon):
|
||||
p = tor_provider.Provider("basedir", FakeConfig(launch=True),
|
||||
reactor)
|
||||
h = p.get_tor_handler()
|
||||
self.assertIs(h, handler)
|
||||
tor.control_endpoint_maker.assert_called_with(p._make_control_endpoint)
|
||||
tor.control_endpoint_maker.assert_called_with(p._make_control_endpoint,
|
||||
takes_status=True)
|
||||
|
||||
# make sure Tor is launched just once, the first time an endpoint is
|
||||
# requested, and never again. The clientFromString() function is
|
||||
@ -316,7 +326,8 @@ class Provider(unittest.TestCase):
|
||||
cfs = mock.Mock(return_value=ep)
|
||||
with mock.patch("allmydata.util.tor_provider._launch_tor", launch_tor):
|
||||
with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
|
||||
d = p._make_control_endpoint(reactor)
|
||||
d = p._make_control_endpoint(reactor,
|
||||
update_status=lambda status: None)
|
||||
yield flushEventualQueue()
|
||||
self.assertIs(self.successResultOf(d), ep)
|
||||
launch_tor.assert_called_with(reactor, None,
|
||||
@ -328,7 +339,8 @@ class Provider(unittest.TestCase):
|
||||
cfs2 = mock.Mock(return_value=ep)
|
||||
with mock.patch("allmydata.util.tor_provider._launch_tor", launch_tor2):
|
||||
with mock.patch("allmydata.util.tor_provider.clientFromString", cfs2):
|
||||
d2 = p._make_control_endpoint(reactor)
|
||||
d2 = p._make_control_endpoint(reactor,
|
||||
update_status=lambda status: None)
|
||||
yield flushEventualQueue()
|
||||
self.assertIs(self.successResultOf(d2), ep)
|
||||
self.assertEqual(launch_tor2.mock_calls, [])
|
||||
|
@ -2,6 +2,7 @@ from twisted.trial import unittest
|
||||
|
||||
from ...storage_client import NativeStorageServer
|
||||
from ...web.root import Root
|
||||
from ...util.connection_status import ConnectionStatus
|
||||
|
||||
class FakeRoot(Root):
|
||||
def __init__(self):
|
||||
@ -26,6 +27,8 @@ class RenderServiceRow(unittest.TestCase):
|
||||
"permutation-seed-base32": "w2hqnbaa25yw4qgcvghl5psa3srpfgw3",
|
||||
}
|
||||
s = NativeStorageServer("server_id", ann, None, {})
|
||||
cs = ConnectionStatus(False, "summary", "description", 0, 0)
|
||||
s.get_connection_status = lambda: cs
|
||||
|
||||
r = FakeRoot()
|
||||
ctx = FakeContext()
|
||||
|
@ -20,6 +20,7 @@ from allmydata.web import status
|
||||
from allmydata.util import fileutil, base32, hashutil
|
||||
from allmydata.util.consumer import download_to_data
|
||||
from allmydata.util.encodingutil import to_str
|
||||
from ...util.connection_status import ConnectionStatus
|
||||
from ..common import FakeCHKFileNode, FakeMutableFileNode, \
|
||||
create_chk_filenode, WebErrorMixin, \
|
||||
make_mutable_file_uri, create_mutable_filenode
|
||||
@ -164,26 +165,21 @@ class FakeDisplayableServer(StubServer):
|
||||
self.last_loss_time = last_loss_time
|
||||
self.last_rx_time = last_rx_time
|
||||
self.last_connect_time = last_connect_time
|
||||
def on_status_changed(self, cb):
|
||||
def on_status_changed(self, cb): # TODO: try to remove me
|
||||
cb(self)
|
||||
def is_connected(self):
|
||||
def is_connected(self): # TODO: remove me
|
||||
return self.connected
|
||||
def get_permutation_seed(self):
|
||||
return ""
|
||||
def get_remote_host(self):
|
||||
return ""
|
||||
def get_last_loss_time(self):
|
||||
return self.last_loss_time
|
||||
def get_last_received_data_time(self):
|
||||
return self.last_rx_time
|
||||
def get_last_connect_time(self):
|
||||
return self.last_connect_time
|
||||
def get_announcement(self):
|
||||
return self.announcement
|
||||
def get_nickname(self):
|
||||
return self.announcement["nickname"]
|
||||
def get_available_space(self):
|
||||
return 123456
|
||||
def get_connection_status(self):
|
||||
return ConnectionStatus(self.connected, "summary", "description",
|
||||
self.last_connect_time, self.last_rx_time)
|
||||
|
||||
class FakeBucketCounter(object):
|
||||
def get_state(self):
|
||||
@ -243,7 +239,7 @@ class FakeClient(Client):
|
||||
self.storage_broker.test_add_server("disconnected_nodeid",
|
||||
FakeDisplayableServer(
|
||||
serverid="other_nodeid", nickname=u"disconnected_nickname \u263B", connected = False,
|
||||
last_connect_time = 15, last_loss_time = 25, last_rx_time = 35))
|
||||
last_connect_time = None, last_loss_time = 25, last_rx_time = 35))
|
||||
self.introducer_client = None
|
||||
self.history = FakeHistory()
|
||||
self.uploader = FakeUploader()
|
||||
@ -611,6 +607,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
|
||||
def test_welcome(self):
|
||||
d = self.GET("/")
|
||||
def _check(res):
|
||||
# TODO: replace this with a parser
|
||||
self.failUnlessIn('<title>Tahoe-LAFS - Welcome</title>', res)
|
||||
self.failUnlessIn(FAVICON_MARKUP, res)
|
||||
self.failUnlessIn('<a href="status">Recent and Active Operations</a>', res)
|
||||
@ -624,14 +621,27 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
|
||||
self.failUnlessIn(u'Connected to <span>1</span>\n of <span>2</span> known storage servers', res_u)
|
||||
def timestamp(t):
|
||||
return (u'"%s"' % (t,)) if self.have_working_tzset() else u'"[^"]*"'
|
||||
|
||||
# TODO: use a real parser to make sure these two nodes are siblings
|
||||
self.failUnless(re.search(
|
||||
u'<div class="status-indicator"><img (src="img/connected-yes.png" |alt="Connected" ){2}/>'
|
||||
u'</div>\n <a( class="timestamp"| title=%s){2}>1d\u00A00h\u00A00m\u00A050s</a>'
|
||||
u'<div class="status-indicator"><img (src="img/connected-yes.png" |alt="Connected" ){2}/></div>'
|
||||
u'\s+'
|
||||
u'<div class="nickname">other_nickname \u263B</div>',
|
||||
res_u), repr(res_u))
|
||||
self.failUnless(re.search(
|
||||
u'<a( class="timestamp"| title=%s){2}>\s+1d\u00A00h\u00A00m\u00A050s\s+</a>'
|
||||
% timestamp(u'1970-01-01 13:00:10'), res_u), repr(res_u))
|
||||
|
||||
# same for these two nodes
|
||||
self.failUnless(re.search(
|
||||
u'<div class="status-indicator"><img (src="img/connected-no.png" |alt="Disconnected" ){2}/>'
|
||||
u'</div>\n <a( class="timestamp"| title=%s){2}>1d\u00A00h\u00A00m\u00A035s</a>'
|
||||
% timestamp(u'1970-01-01 13:00:25'), res_u), repr(res_u))
|
||||
u'<div class="status-indicator"><img (src="img/connected-no.png" |alt="Disconnected" ){2}/></div>'
|
||||
u'\s+'
|
||||
u'<div class="nickname">disconnected_nickname \u263B</div>',
|
||||
res_u), repr(res_u))
|
||||
self.failUnless(re.search(
|
||||
u'<a( class="timestamp"| title="N/A"){2}>\s+N/A\s+</a>',
|
||||
res_u), repr(res_u))
|
||||
|
||||
self.failUnless(re.search(
|
||||
u'<td class="service-last-received-data"><a( class="timestamp"| title=%s){2}>'
|
||||
u'1d\u00A00h\u00A00m\u00A030s</a></td>'
|
||||
@ -656,12 +666,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
|
||||
class MockIntroducerClient(object):
|
||||
def __init__(self, connected):
|
||||
self.connected = connected
|
||||
def connected_to_introducer(self):
|
||||
return self.connected
|
||||
def get_since(self):
|
||||
return 0
|
||||
def get_last_received_data_time(self):
|
||||
return 0
|
||||
def connection_status(self):
|
||||
return ConnectionStatus(self.connected,
|
||||
"summary", "description", 0, 0)
|
||||
|
||||
d = defer.succeed(None)
|
||||
|
||||
@ -673,7 +680,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
|
||||
d.addCallback(_set_introducer_not_connected_unguessable)
|
||||
def _check_introducer_not_connected_unguessable(res):
|
||||
html = res.replace('\n', ' ')
|
||||
self.failUnlessIn('<div class="furl">pb://someIntroducer/[censored]</div>', html)
|
||||
self.failUnlessIn('<div class="connection-status" title="description">summary</div>', html)
|
||||
self.failIfIn('pb://someIntroducer/secret', html)
|
||||
self.failUnless(re.search('<img (alt="Disconnected" |src="img/connected-no.png" ){2}/></div>[ ]*<div>No introducers connected</div>', html), res)
|
||||
|
||||
@ -687,7 +694,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
|
||||
d.addCallback(_set_introducer_connected_unguessable)
|
||||
def _check_introducer_connected_unguessable(res):
|
||||
html = res.replace('\n', ' ')
|
||||
self.failUnlessIn('<div class="furl">pb://someIntroducer/[censored]</div>', html)
|
||||
self.failUnlessIn('<div class="connection-status" title="description">summary</div>', html)
|
||||
self.failIfIn('pb://someIntroducer/secret', html)
|
||||
self.failUnless(re.search('<img (src="img/connected-yes.png" |alt="Connected" ){2}/></div>[ ]*<div>1 introducer connected</div>', html), res)
|
||||
d.addCallback(_check_introducer_connected_unguessable)
|
||||
@ -700,7 +707,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
|
||||
d.addCallback(_set_introducer_connected_guessable)
|
||||
def _check_introducer_connected_guessable(res):
|
||||
html = res.replace('\n', ' ')
|
||||
self.failUnlessIn('<div class="furl">pb://someIntroducer/introducer</div>', html)
|
||||
self.failUnlessIn('<div class="connection-status" title="description">summary</div>', html)
|
||||
self.failUnless(re.search('<img (src="img/connected-yes.png" |alt="Connected" ){2}/></div>[ ]*<div>1 introducer connected</div>', html), res)
|
||||
d.addCallback(_check_introducer_connected_guessable)
|
||||
return d
|
||||
|
81
src/allmydata/util/connection_status.py
Normal file
81
src/allmydata/util/connection_status.py
Normal 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
|
@ -226,7 +226,8 @@ class Provider(service.MultiService):
|
||||
if self._get_tor_config("launch", False, boolean=True):
|
||||
if not self._txtorcon:
|
||||
return None
|
||||
return self._tor.control_endpoint_maker(self._make_control_endpoint)
|
||||
return self._tor.control_endpoint_maker(self._make_control_endpoint,
|
||||
takes_status=True)
|
||||
|
||||
socks_endpoint_desc = self._get_tor_config("socks.port", None)
|
||||
if socks_endpoint_desc:
|
||||
@ -241,9 +242,11 @@ class Provider(service.MultiService):
|
||||
return self._tor.default_socks()
|
||||
|
||||
@inlineCallbacks
|
||||
def _make_control_endpoint(self, reactor):
|
||||
def _make_control_endpoint(self, reactor, update_status):
|
||||
# this will only be called when tahoe.cfg has "[tor] launch = true"
|
||||
(endpoint_desc, _) = yield self._get_launched_tor(reactor)
|
||||
update_status("launching Tor")
|
||||
with self._tor.add_context(update_status, "launching Tor"):
|
||||
(endpoint_desc, _) = yield self._get_launched_tor(reactor)
|
||||
tor_control_endpoint = clientFromString(reactor, endpoint_desc)
|
||||
returnValue(tor_control_endpoint)
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import time, os
|
||||
|
||||
from twisted.internet import address
|
||||
from twisted.web import http
|
||||
from nevow import rend, url, tags as T
|
||||
from nevow.inevow import IRequest
|
||||
@ -240,18 +239,14 @@ class Root(rend.Page):
|
||||
return "%s introducers connected" % (connected_count,)
|
||||
|
||||
def data_total_introducers(self, ctx, data):
|
||||
return len(self.client.introducer_furls)
|
||||
return len(self.client.introducer_connection_statuses())
|
||||
|
||||
def data_connected_introducers(self, ctx, data):
|
||||
return self.client.introducer_connection_statuses().count(True)
|
||||
|
||||
def data_connected_to_introducer(self, ctx, data):
|
||||
if self.client.connected_to_introducer():
|
||||
return "yes"
|
||||
return "no"
|
||||
return len([1 for cs in self.client.introducer_connection_statuses()
|
||||
if cs.connected])
|
||||
|
||||
def data_connected_to_at_least_one_introducer(self, ctx, data):
|
||||
if True in self.client.introducer_connection_statuses():
|
||||
if self.data_connected_introducers(ctx, data):
|
||||
return "yes"
|
||||
return "no"
|
||||
|
||||
@ -260,42 +255,35 @@ class Root(rend.Page):
|
||||
|
||||
# In case we configure multiple introducers
|
||||
def data_introducers(self, ctx, data):
|
||||
connection_statuses = self.client.introducer_connection_statuses()
|
||||
s = []
|
||||
furls = self.client.introducer_furls
|
||||
for furl in furls:
|
||||
if connection_statuses:
|
||||
display_furl = furl
|
||||
# trim off the secret swissnum
|
||||
(prefix, _, swissnum) = furl.rpartition("/")
|
||||
if swissnum != "introducer":
|
||||
display_furl = "%s/[censored]" % (prefix,)
|
||||
i = furls.index(furl)
|
||||
ic = self.client.introducer_clients[i]
|
||||
s.append((display_furl, bool(connection_statuses[i]), ic))
|
||||
s.sort()
|
||||
return s
|
||||
return self.client.introducer_connection_statuses()
|
||||
|
||||
def render_introducers_row(self, ctx, s):
|
||||
(furl, connected, ic) = s
|
||||
service_connection_status = "yes" if connected else "no"
|
||||
|
||||
since = ic.get_since()
|
||||
service_connection_status_rel_time = render_time_delta(since, self.now_fn())
|
||||
service_connection_status_abs_time = render_time_attr(since)
|
||||
|
||||
last_received_data_time = ic.get_last_received_data_time()
|
||||
last_received_data_rel_time = render_time_delta(last_received_data_time, self.now_fn())
|
||||
last_received_data_abs_time = render_time_attr(last_received_data_time)
|
||||
|
||||
ctx.fillSlots("introducer_furl", "%s" % (furl))
|
||||
ctx.fillSlots("service_connection_status", "%s" % (service_connection_status,))
|
||||
def render_introducers_row(self, ctx, cs):
|
||||
connected = "yes" if cs.connected else "no"
|
||||
ctx.fillSlots("service_connection_status", connected)
|
||||
ctx.fillSlots("service_connection_status_alt",
|
||||
self._connectedalts[service_connection_status])
|
||||
ctx.fillSlots("service_connection_status_abs_time", service_connection_status_abs_time)
|
||||
ctx.fillSlots("service_connection_status_rel_time", service_connection_status_rel_time)
|
||||
ctx.fillSlots("last_received_data_abs_time", last_received_data_abs_time)
|
||||
ctx.fillSlots("last_received_data_rel_time", last_received_data_rel_time)
|
||||
self._connectedalts[connected])
|
||||
|
||||
since = cs.last_connection_time
|
||||
ctx.fillSlots("service_connection_status_rel_time",
|
||||
render_time_delta(since, self.now_fn())
|
||||
if since is not None
|
||||
else "N/A")
|
||||
ctx.fillSlots("service_connection_status_abs_time",
|
||||
render_time_attr(since)
|
||||
if since is not None
|
||||
else "N/A")
|
||||
|
||||
last_received_data_time = cs.last_received_time
|
||||
ctx.fillSlots("last_received_data_abs_time",
|
||||
render_time_attr(last_received_data_time)
|
||||
if last_received_data_time is not None
|
||||
else "N/A")
|
||||
ctx.fillSlots("last_received_data_rel_time",
|
||||
render_time_delta(last_received_data_time, self.now_fn())
|
||||
if last_received_data_time is not None
|
||||
else "N/A")
|
||||
ctx.fillSlots("summary", "%s" % cs.last_connection_summary)
|
||||
ctx.fillSlots("details", "%s" % cs.last_connection_description)
|
||||
return ctx.tag
|
||||
|
||||
def data_helper_furl_prefix(self, ctx, data):
|
||||
@ -344,33 +332,38 @@ class Root(rend.Page):
|
||||
return sorted(sb.get_known_servers(), key=lambda s: s.get_serverid())
|
||||
|
||||
def render_service_row(self, ctx, server):
|
||||
server_id = server.get_serverid()
|
||||
cs = server.get_connection_status()
|
||||
|
||||
ctx.fillSlots("peerid", server.get_longname())
|
||||
ctx.fillSlots("nickname", server.get_nickname())
|
||||
rhost = server.get_remote_host()
|
||||
if server.is_connected():
|
||||
if server_id == self.client.get_long_nodeid():
|
||||
rhost_s = "(loopback)"
|
||||
elif isinstance(rhost, address.IPv4Address):
|
||||
rhost_s = "%s:%d" % (rhost.host, rhost.port)
|
||||
else:
|
||||
rhost_s = str(rhost)
|
||||
addr = rhost_s
|
||||
service_connection_status = "yes"
|
||||
last_connect_time = server.get_last_connect_time()
|
||||
service_connection_status_rel_time = render_time_delta(last_connect_time, self.now_fn())
|
||||
service_connection_status_abs_time = render_time_attr(last_connect_time)
|
||||
else:
|
||||
addr = "N/A"
|
||||
service_connection_status = "no"
|
||||
last_loss_time = server.get_last_loss_time()
|
||||
service_connection_status_rel_time = render_time_delta(last_loss_time, self.now_fn())
|
||||
service_connection_status_abs_time = render_time_attr(last_loss_time)
|
||||
|
||||
last_received_data_time = server.get_last_received_data_time()
|
||||
last_received_data_rel_time = render_time_delta(last_received_data_time, self.now_fn())
|
||||
last_received_data_abs_time = render_time_attr(last_received_data_time)
|
||||
connected = "yes" if cs.connected else "no"
|
||||
ctx.fillSlots("service_connection_status", connected)
|
||||
ctx.fillSlots("service_connection_status_alt",
|
||||
self._connectedalts[connected])
|
||||
|
||||
since = cs.last_connection_time
|
||||
ctx.fillSlots("service_connection_status_rel_time",
|
||||
render_time_delta(since, self.now_fn())
|
||||
if since is not None
|
||||
else "N/A")
|
||||
ctx.fillSlots("service_connection_status_abs_time",
|
||||
render_time_attr(since)
|
||||
if since is not None
|
||||
else "N/A")
|
||||
|
||||
last_received_data_time = cs.last_received_time
|
||||
ctx.fillSlots("last_received_data_abs_time",
|
||||
render_time_attr(last_received_data_time)
|
||||
if last_received_data_time is not None
|
||||
else "N/A")
|
||||
ctx.fillSlots("last_received_data_rel_time",
|
||||
render_time_delta(last_received_data_time, self.now_fn())
|
||||
if last_received_data_time is not None
|
||||
else "N/A")
|
||||
|
||||
ctx.fillSlots("summary", "%s" % cs.last_connection_summary)
|
||||
ctx.fillSlots("details", "%s" % cs.last_connection_description)
|
||||
|
||||
announcement = server.get_announcement()
|
||||
version = announcement.get("my-version", "")
|
||||
@ -379,14 +372,6 @@ class Root(rend.Page):
|
||||
available_space = "N/A"
|
||||
else:
|
||||
available_space = abbreviate_size(available_space)
|
||||
ctx.fillSlots("address", addr)
|
||||
ctx.fillSlots("service_connection_status", service_connection_status)
|
||||
ctx.fillSlots("service_connection_status_alt",
|
||||
self._connectedalts[service_connection_status])
|
||||
ctx.fillSlots("service_connection_status_abs_time", service_connection_status_abs_time)
|
||||
ctx.fillSlots("service_connection_status_rel_time", service_connection_status_rel_time)
|
||||
ctx.fillSlots("last_received_data_abs_time", last_received_data_abs_time)
|
||||
ctx.fillSlots("last_received_data_rel_time", last_received_data_rel_time)
|
||||
ctx.fillSlots("version", version)
|
||||
ctx.fillSlots("available_space", available_space)
|
||||
|
||||
|
@ -50,6 +50,9 @@ body {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
}
|
||||
|
||||
.furl {
|
||||
font-size: 0.8em;
|
||||
word-wrap: break-word;
|
||||
@ -78,7 +81,7 @@ body {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.nickname-and-peerid .timestamp {
|
||||
.timestamp {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
@ -176,7 +176,7 @@
|
||||
<thead>
|
||||
<tr n:pattern="header">
|
||||
<td><h3>Nickname</h3></td>
|
||||
<td><h3>Address</h3></td>
|
||||
<td><h3>Connection</h3></td>
|
||||
<td><h3>Last RX</h3></td>
|
||||
<td><h3>Version</h3></td>
|
||||
<td><h3>Available</h3></td>
|
||||
@ -185,16 +185,22 @@
|
||||
<tr n:pattern="item" n:render="service_row">
|
||||
<td class="nickname-and-peerid">
|
||||
<div class="status-indicator"><img><n:attr name="src">img/connected-<n:slot name="service_connection_status" />.png</n:attr><n:attr name="alt"><n:slot name="service_connection_status_alt" /></n:attr></img></div>
|
||||
<a class="timestamp"><n:attr name="title"><n:slot name="service_connection_status_abs_time"/></n:attr><n:slot name="service_connection_status_rel_time"/></a>
|
||||
<div class="nickname"><n:slot name="nickname"/></div>
|
||||
<div class="nodeid"><n:slot name="peerid"/></div>
|
||||
</td>
|
||||
<td class="address"><n:slot name="address"/></td>
|
||||
<td class="connection-status">
|
||||
<n:attr name="title"><n:slot name="details"/></n:attr>
|
||||
<n:slot name="summary"/>
|
||||
<a class="timestamp">
|
||||
<n:attr name="title"><n:slot name="service_connection_status_abs_time"/></n:attr>
|
||||
<n:slot name="service_connection_status_rel_time"/>
|
||||
</a>
|
||||
</td>
|
||||
<td class="service-last-received-data"><a class="timestamp"><n:attr name="title"><n:slot name="last_received_data_abs_time"/></n:attr><n:slot name="last_received_data_rel_time"/></a></td>
|
||||
<td class="service-version"><n:slot name="version"/></td>
|
||||
<td class="service-available-space"><n:slot name="available_space"/></td>
|
||||
</tr>
|
||||
<tr n:pattern="empty"><td colspan="5">You are not presently connected to any peers</td></tr>
|
||||
<tr n:pattern="empty"><td colspan="5">You are not presently connected to any servers.</td></tr>
|
||||
</table>
|
||||
<div class="row-fluid">
|
||||
<h2>Connected to <span n:render="string" n:data="connected_introducers" /> of <span n:render="string" n:data="total_introducers" /> introducers</h2>
|
||||
@ -202,7 +208,7 @@
|
||||
<table class="table table-striped table-bordered peer-status" n:render="sequence" n:data="introducers">
|
||||
<thead>
|
||||
<tr n:pattern="header">
|
||||
<td><h3>Address</h3></td>
|
||||
<td><h3>Connection</h3></td>
|
||||
<td><h3>Last RX</h3></td>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -210,7 +216,7 @@
|
||||
<td class="nickname-and-peerid">
|
||||
<div class="status-indicator"><img><n:attr name="src">img/connected-<n:slot name="service_connection_status" />.png</n:attr><n:attr name="alt"><n:slot name="service_connection_status_alt" /></n:attr></img></div>
|
||||
<a class="timestamp"><n:attr name="title"><n:slot name="service_connection_status_abs_time"/></n:attr><n:slot name="service_connection_status_rel_time"/></a>
|
||||
<div class="furl"><n:slot name="introducer_furl"/></div>
|
||||
<div class="connection-status"><n:attr name="title"><n:slot name="details"/></n:attr><n:slot name="summary"/></div>
|
||||
</td>
|
||||
<td class="service-last-received-data"><a class="timestamp"><n:attr name="title"><n:slot name="last_received_data_abs_time"/></n:attr><n:slot name="last_received_data_rel_time"/></a></td>
|
||||
</tr>
|
||||
|
Loading…
Reference in New Issue
Block a user