diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py
index 3600b5bfa..b73247eb5 100644
--- a/src/allmydata/interfaces.py
+++ b/src/allmydata/interfaces.py
@@ -2857,7 +2857,7 @@ class IConnectionStatus(Interface):
negotiation was successful. Otherwise it is None.
""")
- last_connection_summary = Attribute(
+ summary = Attribute(
"""
A string with a brief summary of the current status, suitable for
display on an informational page. The more complete text from
@@ -2865,21 +2865,6 @@ class IConnectionStatus(Interface):
popup.
""")
- last_connection_description = Attribute(
- """
- 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(
"""
A timestamp (seconds-since-epoch) describing the last time we heard
@@ -2887,3 +2872,14 @@ class IConnectionStatus(Interface):
the other side.
""")
+ non_connected_statuses = Attribute(
+ """
+ A dictionary, describing all connections that are not (yet)
+ successful. When connected is True, this will only be the losing
+ attempts. When connected is False, this will include all attempts.
+
+ This maps a connection description string (for foolscap this is a
+ connection hint and the handler it is using) to the status string
+ (pending, connected, refused, or other errors).
+ """)
+
diff --git a/src/allmydata/test/test_connections.py b/src/allmydata/test/test_connections.py
index 7c9767e1d..c336a5c3c 100644
--- a/src/allmydata/test/test_connections.py
+++ b/src/allmydata/test/test_connections.py
@@ -345,11 +345,13 @@ class Privacy(unittest.TestCase):
self.assertEqual(str(e), "tub.location includes tcp: hint")
class Status(unittest.TestCase):
- def test_describe(self):
- t = connection_status._describe_statuses(["h2","h1"],
- {"h1": "hand1"},
- {"h1": "st1", "h2": "st2"})
- self.assertEqual(t, " h1 via hand1: st1\n h2: st2\n")
+ def test_hint_statuses(self):
+ ncs = connection_status._hint_statuses(["h2","h1"],
+ {"h1": "hand1", "h4": "hand4"},
+ {"h1": "st1", "h2": "st2",
+ "h3": "st3"})
+ self.assertEqual(ncs, {"h1 via hand1": "st1",
+ "h2": "st2"})
def test_reconnector_connected(self):
ci = mock.Mock()
@@ -364,10 +366,8 @@ class Status(unittest.TestCase):
rc.getReconnectionInfo = mock.Mock(return_value=ri)
cs = connection_status.from_foolscap_reconnector(rc, 123)
self.assertEqual(cs.connected, True)
- self.assertEqual(cs.last_connection_summary,
- "Connected to h1 via hand1")
- self.assertEqual(cs.last_connection_description,
- "Connection successful to h1 via hand1")
+ self.assertEqual(cs.summary, "Connected to h1 via hand1")
+ self.assertEqual(cs.non_connected_statuses, {})
self.assertEqual(cs.last_connection_time, 120)
self.assertEqual(cs.last_received_time, 123)
@@ -384,12 +384,8 @@ class Status(unittest.TestCase):
rc.getReconnectionInfo = mock.Mock(return_value=ri)
cs = connection_status.from_foolscap_reconnector(rc, 123)
self.assertEqual(cs.connected, True)
- self.assertEqual(cs.last_connection_summary,
- "Connected to h1 via hand1")
- self.assertEqual(cs.last_connection_description,
- "Connection successful to h1 via hand1\n"
- "other hints:\n"
- " h2: st2\n")
+ self.assertEqual(cs.summary, "Connected to h1 via hand1")
+ self.assertEqual(cs.non_connected_statuses, {"h2": "st2"})
self.assertEqual(cs.last_connection_time, 120)
self.assertEqual(cs.last_received_time, 123)
@@ -407,13 +403,9 @@ class Status(unittest.TestCase):
rc.getReconnectionInfo = mock.Mock(return_value=ri)
cs = connection_status.from_foolscap_reconnector(rc, 123)
self.assertEqual(cs.connected, True)
- self.assertEqual(cs.last_connection_summary,
- "Connected via listener (listener1)")
- self.assertEqual(cs.last_connection_description,
- "Connection successful via listener (listener1)\n"
- "other hints:\n"
- " h1 via hand1: st1\n"
- " h2: st2\n")
+ self.assertEqual(cs.summary, "Connected via listener (listener1)")
+ self.assertEqual(cs.non_connected_statuses,
+ {"h1 via hand1": "st1", "h2": "st2"})
self.assertEqual(cs.last_connection_time, 120)
self.assertEqual(cs.last_received_time, 123)
@@ -428,12 +420,9 @@ class Status(unittest.TestCase):
rc.getReconnectionInfo = mock.Mock(return_value=ri)
cs = connection_status.from_foolscap_reconnector(rc, 123)
self.assertEqual(cs.connected, False)
- self.assertEqual(cs.last_connection_summary,
- "Trying to connect")
- self.assertEqual(cs.last_connection_description,
- "Trying to connect:\n"
- " h1 via hand1: st1\n"
- " h2: st2\n")
+ self.assertEqual(cs.summary, "Trying to connect")
+ self.assertEqual(cs.non_connected_statuses,
+ {"h1 via hand1": "st1", "h2": "st2"})
self.assertEqual(cs.last_connection_time, None)
self.assertEqual(cs.last_received_time, 123)
@@ -451,13 +440,10 @@ class Status(unittest.TestCase):
with mock.patch("time.time", return_value=12):
cs = connection_status.from_foolscap_reconnector(rc, 5)
self.assertEqual(cs.connected, False)
- self.assertEqual(cs.last_connection_summary,
- "Reconnecting in 8 seconds")
- self.assertEqual(cs.last_connection_description,
- "Reconnecting in 8 seconds\n"
- "Last attempt 2s ago:\n"
- " h1 via hand1: st1\n"
- " h2: st2\n")
+ self.assertEqual(cs.summary,
+ "Reconnecting in 8 seconds (last attempt 2s ago)")
+ self.assertEqual(cs.non_connected_statuses,
+ {"h1 via hand1": "st1", "h2": "st2"})
self.assertEqual(cs.last_connection_time, None)
self.assertEqual(cs.last_received_time, 5)
diff --git a/src/allmydata/test/web/test_root.py b/src/allmydata/test/web/test_root.py
index 8d8232f5a..727189421 100644
--- a/src/allmydata/test/web/test_root.py
+++ b/src/allmydata/test/web/test_root.py
@@ -27,7 +27,7 @@ class RenderServiceRow(unittest.TestCase):
"permutation-seed-base32": "w2hqnbaa25yw4qgcvghl5psa3srpfgw3",
}
s = NativeStorageServer("server_id", ann, None, {})
- cs = ConnectionStatus(False, "summary", "description", 0, 0)
+ cs = ConnectionStatus(False, "summary", {}, 0, 0)
s.get_connection_status = lambda: cs
r = FakeRoot()
diff --git a/src/allmydata/test/web/test_web.py b/src/allmydata/test/web/test_web.py
index 3d6f00b0a..5454703fc 100644
--- a/src/allmydata/test/web/test_web.py
+++ b/src/allmydata/test/web/test_web.py
@@ -178,7 +178,7 @@ class FakeDisplayableServer(StubServer):
def get_available_space(self):
return 123456
def get_connection_status(self):
- return ConnectionStatus(self.connected, "summary", "description",
+ return ConnectionStatus(self.connected, "summary", {},
self.last_connect_time, self.last_rx_time)
class FakeBucketCounter(object):
@@ -667,8 +667,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
def __init__(self, connected):
self.connected = connected
def connection_status(self):
- return ConnectionStatus(self.connected,
- "summary", "description", 0, 0)
+ return ConnectionStatus(self.connected, "summary", {}, 0, 0)
d = defer.succeed(None)
@@ -680,7 +679,6 @@ 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('
summary
', html)
self.failIfIn('pb://someIntroducer/secret', html)
self.failUnless(re.search('[ ]*No introducers connected
', html), res)
@@ -694,7 +692,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('summary
', 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)
@@ -707,7 +705,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('summary
', 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/util/connection_status.py b/src/allmydata/util/connection_status.py
index 4b2fb0185..2ea2b0cab 100644
--- a/src/allmydata/util/connection_status.py
+++ b/src/allmydata/util/connection_status.py
@@ -4,24 +4,22 @@ from ..interfaces import IConnectionStatus
@implementer(IConnectionStatus)
class ConnectionStatus:
- def __init__(self, connected, summary,
- last_connection_description, last_connection_time,
- last_received_time, statuses):
+ def __init__(self, connected, summary, non_connected_statuses,
+ last_connection_time, last_received_time):
self.connected = connected
- self.last_connection_summary = summary
- self.last_connection_description = last_connection_description
+ self.summary = summary
+ self.non_connected_statuses = non_connected_statuses
self.last_connection_time = last_connection_time
self.last_received_time = last_received_time
- self.statuses = statuses
-def _describe_statuses(hints, handlers, statuses):
- descriptions = []
- for hint in sorted(hints):
+def _hint_statuses(which, handlers, statuses):
+ non_connected_statuses = {}
+ for hint in which:
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)
+ dsc = statuses[hint]
+ non_connected_statuses["%s%s" % (hint, handler_dsc)] = dsc
+ return non_connected_statuses
def from_foolscap_reconnector(rc, last_received):
ri = rc.getReconnectionInfo()
@@ -30,53 +28,33 @@ def from_foolscap_reconnector(rc, last_received):
# should never see "unstarted"
assert state in ("connected", "connecting", "waiting"), state
ci = ri.connectionInfo
+ connected = False
+ last_connected = None
+ others = set(ci.connectorStatuses.keys())
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)
+ if ci.winningHint:
+ others.remove(ci.winningHint)
+ summary = "Connected to %s via %s" % (
+ ci.winningHint, ci.connectionHandlers[ci.winningHint])
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
+ summary = "Connected via listener (%s)" % ci.listenerStatus[0]
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
+ summary = "Reconnecting in %d seconds (last attempt %ds ago)" % \
+ (delay, elapsed)
# 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, statuses)
+ non_connected_statuses = _hint_statuses(others,
+ ci.connectionHandlers,
+ ci.connectorStatuses)
+ cs = ConnectionStatus(connected, summary, non_connected_statuses,
+ last_connected, last_received)
return cs
diff --git a/src/allmydata/web/root.py b/src/allmydata/web/root.py
index 1517461f8..17041243c 100644
--- a/src/allmydata/web/root.py
+++ b/src/allmydata/web/root.py
@@ -1,14 +1,11 @@
import time, os
-from datetime import datetime
from twisted.web import http
from nevow import rend, url, tags as T
-from nevow.inevow import IRequest, IContainer
+from nevow.inevow import IRequest
from nevow.static import File as nevow_File # TODO: merge with static.File?
from nevow.util import resource_filename
-from zope.interface import implementer
-
import allmydata # to display import path
from allmydata import get_package_versions_string
from allmydata.util import log
@@ -17,7 +14,6 @@ from allmydata.web import filenode, directory, unlinked, status, operations
from allmydata.web import storage, magic_folder
from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
get_arg, RenderMixin, get_format, get_mutable_type, render_time_delta, render_time, render_time_attr
-from allmydata.util.abbreviate import abbreviate_time
class URIHandler(RenderMixin, rend.Page):
@@ -261,7 +257,7 @@ class Root(rend.Page):
def data_introducers(self, ctx, data):
return self.client.introducer_connection_statuses()
- def render_introducers_row(self, ctx, cs):
+ def _render_connection_status(self, ctx, cs):
connected = "yes" if cs.connected else "no"
ctx.fillSlots("service_connection_status", connected)
ctx.fillSlots("service_connection_status_alt",
@@ -286,8 +282,25 @@ class Root(rend.Page):
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)
+
+ others = cs.non_connected_statuses
+ if cs.connected:
+ ctx.fillSlots("summary", cs.summary)
+ if others:
+ details = "\n".join(["* %s: %s\n" % (which, others[which])
+ for which in sorted(others)])
+ ctx.fillSlots("details", "Other hints:\n" + details)
+ else:
+ ctx.fillSlots("details", "(no other hints)")
+ else:
+ details = T.ul()
+ for which in sorted(others):
+ details[T.li["%s: %s" % (which, others[which])]]
+ ctx.fillSlots("summary", [cs.summary, details])
+ ctx.fillSlots("details", "")
+
+ def render_introducers_row(self, ctx, cs):
+ self._render_connection_status(ctx, cs)
return ctx.tag
def data_helper_furl_prefix(self, ctx, data):
@@ -333,75 +346,15 @@ class Root(rend.Page):
def data_services(self, ctx, data):
sb = self.client.get_storage_broker()
-
- @implementer(IContainer)
- class Wrapper(object):
- """
- This provides IContainer to Nevow so we can provide a 'child' data
- at 'connections' and pass-through all the
- connection-status attributes to the rest of the renderers
- """
- def __init__(self, server):
- self._server = server
-
- def child(self, context, name):
- if name == 'connections':
- st = self._server.get_connection_status()
- if st.connected:
- # we don't want the list of all possible hints
- # when we're already connected; then we just
- # show the one connection
- return []
- return st.statuses.items()
- return None # or are we supposed to raise something?
-
- def __getattr__(self, x):
- return getattr(self._server, x)
-
- return [
- Wrapper(x)
- for x in sorted(sb.get_known_servers(), key=lambda s: s.get_serverid())
- ]
-
- def render_connection_item(self, ctx, server):
- ctx.tag.fillSlots('details', server[1])
- ctx.tag.fillSlots('summary', '{}: {}'.format(server[0], server[1]))
- return ctx.tag
+ return sorted(sb.get_known_servers(), key=lambda s: s.get_serverid())
def render_service_row(self, ctx, server):
cs = server.get_connection_status()
+ self._render_connection_status(ctx, cs)
ctx.fillSlots("peerid", server.get_longname())
ctx.fillSlots("nickname", server.get_nickname())
- 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", "")
available_space = server.get_available_space()
diff --git a/src/allmydata/web/welcome.xhtml b/src/allmydata/web/welcome.xhtml
index 1028cd3e4..1c043be88 100644
--- a/src/allmydata/web/welcome.xhtml
+++ b/src/allmydata/web/welcome.xhtml
@@ -188,20 +188,16 @@
-
+ |
-
-
-
|
+
|
|
|