From de4295ae60d2391c5574838720a8f55018090945 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 7 Dec 2016 21:42:00 -0800 Subject: [PATCH 1/5] require foolscap >= 0.12.5, for ReconnectionInfo --- setup.py | 4 ++-- src/allmydata/_auto_deps.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 8c779c400..bd032ca3f 100644 --- a/setup.py +++ b/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": [ diff --git a/src/allmydata/_auto_deps.py b/src/allmydata/_auto_deps.py index 2ed6c264f..7349e78ae 100644 --- a/src/allmydata/_auto_deps.py +++ b/src/allmydata/_auto_deps.py @@ -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 From 8d008967e71b23035280aa01640fdfbbf6f3a2ad Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 8 Dec 2016 15:11:45 -0800 Subject: [PATCH 2/5] tor_provider: use new Foolscap API to provide better status --- src/allmydata/test/test_connections.py | 7 +++++-- src/allmydata/test/test_tor_provider.py | 18 +++++++++++++++--- src/allmydata/util/tor_provider.py | 9 ++++++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/allmydata/test/test_connections.py b/src/allmydata/test/test_connections.py index df783f784..0683ced87 100644 --- a/src/allmydata/test/test_connections.py +++ b/src/allmydata/test/test_connections.py @@ -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") diff --git a/src/allmydata/test/test_tor_provider.py b/src/allmydata/test/test_tor_provider.py index 2e09ddf9f..dbb74e4b2 100644 --- a/src/allmydata/test/test_tor_provider.py +++ b/src/allmydata/test/test_tor_provider.py @@ -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, []) diff --git a/src/allmydata/util/tor_provider.py b/src/allmydata/util/tor_provider.py index f20ed7f6a..1c930c14e 100644 --- a/src/allmydata/util/tor_provider.py +++ b/src/allmydata/util/tor_provider.py @@ -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) From 48fc14bd3052fb1b81ec7a15b314e57afe1e0104 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 8 Dec 2016 15:09:51 -0800 Subject: [PATCH 3/5] add IConnectionStatus and implementation also a function to build one from a foolscap.Reconnector --- src/allmydata/interfaces.py | 57 +++++++++++++++++ src/allmydata/util/connection_status.py | 81 +++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/allmydata/util/connection_status.py diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index 6a9c352cb..0faf773db 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -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. + """) + diff --git a/src/allmydata/util/connection_status.py b/src/allmydata/util/connection_status.py new file mode 100644 index 000000000..a4f425746 --- /dev/null +++ b/src/allmydata/util/connection_status.py @@ -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 From 77fd41b66edeea4f9fa31fcb28c82a7774b790b4 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 8 Dec 2016 15:15:49 -0800 Subject: [PATCH 4/5] update WUI welcome page with new connection-status info This shows current-connection info, and provides per-hint status details in a tooltip. The "Connection" section no longer shows seconds-since-loss when the server was not connected (previously it showed seconds-since-connect when connected, and flipped to seconds-since-loss when disconnected). We already have the "Last RX" column, which is arguably more meaningful (and I can't think of a good case when these would differ), so we don't really need seconds-since-loss, and the new ConnectionStatus doesn't track it anyways. So now the "Connection" timestamp for non-connected servers is just "N/A" (both the main text and the tooltip). The "Introducers" section was changed the same way. This moves the per-server connection timestamp out of the nickname/serverid box and over into the Connection box. It also right-floats all timestamps, regardless of which box they're in, which makes them share the box with connection_status more politely. Internally, this adds code to create ConnectionStatus objects when necessary. --- src/allmydata/client.py | 2 +- src/allmydata/introducer/client.py | 10 +- src/allmydata/storage_client.py | 10 +- src/allmydata/test/web/test_root.py | 3 + src/allmydata/test/web/test_web.py | 39 ++++-- src/allmydata/web/root.py | 133 +++++++++------------ src/allmydata/web/static/css/new-tahoe.css | 5 +- src/allmydata/web/welcome.xhtml | 18 ++- 8 files changed, 127 insertions(+), 93 deletions(-) diff --git a/src/allmydata/client.py b/src/allmydata/client.py index d9c77b7e3..669d962f5 100644 --- a/src/allmydata/client.py +++ b/src/allmydata/client.py @@ -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]) diff --git a/src/allmydata/introducer/client.py b/src/allmydata/introducer/client.py index e1643e8ac..83ce2975c 100644 --- a/src/allmydata/introducer/client.py +++ b/src/allmydata/introducer/client.py @@ -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,6 +326,14 @@ 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) diff --git a/src/allmydata/storage_client.py b/src/allmydata/storage_client.py index 64a6985f1..73736f2ad 100644 --- a/src/allmydata/storage_client.py +++ b/src/allmydata/storage_client.py @@ -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,6 +364,14 @@ 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): diff --git a/src/allmydata/test/web/test_root.py b/src/allmydata/test/web/test_root.py index e0bfa779b..8d8232f5a 100644 --- a/src/allmydata/test/web/test_root.py +++ b/src/allmydata/test/web/test_root.py @@ -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() diff --git a/src/allmydata/test/web/test_web.py b/src/allmydata/test/web/test_web.py index 3914d02be..e607c8a6e 100644 --- a/src/allmydata/test/web/test_web.py +++ b/src/allmydata/test/web/test_web.py @@ -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 @@ -184,6 +185,9 @@ class FakeDisplayableServer(StubServer): 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 +247,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 +615,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('Tahoe-LAFS - Welcome', res) self.failUnlessIn(FAVICON_MARKUP, res) self.failUnlessIn('Recent and Active Operations', res) @@ -624,14 +629,27 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi self.failUnlessIn(u'Connected to 1\n of 2 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'
' - u'
\n 1d\u00A00h\u00A00m\u00A050s' + u'
' + u'\s+' + u'
other_nickname \u263B
', + res_u), repr(res_u)) + self.failUnless(re.search( + u'\s+1d\u00A00h\u00A00m\u00A050s\s+' % timestamp(u'1970-01-01 13:00:10'), res_u), repr(res_u)) + + # same for these two nodes self.failUnless(re.search( - u'
' - u'
\n 1d\u00A00h\u00A00m\u00A035s' - % timestamp(u'1970-01-01 13:00:25'), res_u), repr(res_u)) + u'
' + u'\s+' + u'
disconnected_nickname \u263B
', + res_u), repr(res_u)) + self.failUnless(re.search( + u'\s+N/A\s+', + res_u), repr(res_u)) + self.failUnless(re.search( u'' u'1d\u00A00h\u00A00m\u00A030s' @@ -662,6 +680,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi 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 +694,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('
pb://someIntroducer/[censored]
', html) + self.failUnlessIn('
summary
', html) self.failIfIn('pb://someIntroducer/secret', html) self.failUnless(re.search('[ ]*
No introducers connected
', html), res) @@ -687,7 +708,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('
pb://someIntroducer/[censored]
', html) + self.failUnlessIn('
summary
', html) self.failIfIn('pb://someIntroducer/secret', html) self.failUnless(re.search('[ ]*
1 introducer connected
', html), res) d.addCallback(_check_introducer_connected_unguessable) @@ -700,7 +721,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('
pb://someIntroducer/introducer
', html) + self.failUnlessIn('
summary
', html) self.failUnless(re.search('[ ]*
1 introducer connected
', html), res) d.addCallback(_check_introducer_connected_guessable) return d diff --git a/src/allmydata/web/root.py b/src/allmydata/web/root.py index 8e4f7c1e8..468275a0e 100644 --- a/src/allmydata/web/root.py +++ b/src/allmydata/web/root.py @@ -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) diff --git a/src/allmydata/web/static/css/new-tahoe.css b/src/allmydata/web/static/css/new-tahoe.css index 8ab7f47d6..38bd662e1 100644 --- a/src/allmydata/web/static/css/new-tahoe.css +++ b/src/allmydata/web/static/css/new-tahoe.css @@ -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; } diff --git a/src/allmydata/web/welcome.xhtml b/src/allmydata/web/welcome.xhtml index 2626374b3..302f5ce0e 100644 --- a/src/allmydata/web/welcome.xhtml +++ b/src/allmydata/web/welcome.xhtml @@ -176,7 +176,7 @@

Nickname

-

Address

+

Connection

Last RX

Version

Available

@@ -185,16 +185,22 @@
img/connected-.png
-
- + + + + + + + + - You are not presently connected to any peers + You are not presently connected to any servers.

Connected to of introducers

@@ -202,7 +208,7 @@ - + @@ -210,7 +216,7 @@ From 38935bbca64adf80e32e4586822bbce6e9be1045 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 8 Dec 2016 15:22:49 -0800 Subject: [PATCH 5/5] remove old unused connection-status APIs --- src/allmydata/introducer/client.py | 6 ------ src/allmydata/storage_client.py | 9 --------- src/allmydata/test/web/test_web.py | 18 ++---------------- 3 files changed, 2 insertions(+), 31 deletions(-) diff --git a/src/allmydata/introducer/client.py b/src/allmydata/introducer/client.py index 83ce2975c..cc3b3dadb 100644 --- a/src/allmydata/introducer/client.py +++ b/src/allmydata/introducer/client.py @@ -339,9 +339,3 @@ class IntroducerClient(service.Service, Referenceable): 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() diff --git a/src/allmydata/storage_client.py b/src/allmydata/storage_client.py index 73736f2ad..8c3854225 100644 --- a/src/allmydata/storage_client.py +++ b/src/allmydata/storage_client.py @@ -374,15 +374,6 @@ class NativeStorageServer(service.MultiService): 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() diff --git a/src/allmydata/test/web/test_web.py b/src/allmydata/test/web/test_web.py index e607c8a6e..3d6f00b0a 100644 --- a/src/allmydata/test/web/test_web.py +++ b/src/allmydata/test/web/test_web.py @@ -165,20 +165,12 @@ 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): @@ -674,12 +666,6 @@ 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)

Address

Connection

Last RX

img/connected-.png
-
+