This significantly cleans up the IntroducerServer web-status renderers.
Instead of poking around in the introducer's internals, now the web-status
renderers get clean AnnouncementDescriptor and SubscriberDescriptor
objects. They are still somewhat foolscap-centric, but will provide a clean
abstraction boundary for future improvements.
The specific #1721 bug was that old (V1) subscribers were handled by
wrapping their RemoteReference in a special WrapV1SubscriberInV2Interface
object, but the web-status display was trying to peek inside the object to
learn what host+port it was associated with, and the wrapper did not proxy
those extra attributes.
A test was added to test_introducer to make sure the introweb page renders
properly and at least contains the nicknames of both the V1 and V2 clients.
import re, simplejson
-from allmydata.util import keyutil, base32
+from foolscap.api import SturdyRef
+from allmydata.util import keyutil, base32, rrefutil
def make_index(ann, key_s):
"""Return something that can be used as an index (e.g. a tuple of
assert type(ver) is str
assert type(oldest) is str
ann = {"version": 0,
- "nickname": nickname.decode("utf-8"),
+ "nickname": nickname.decode("utf-8", "replace"),
"app-versions": {},
"my-version": ver,
"oldest-supported": oldest,
key_vs = claimed_key_vs
ann = simplejson.loads(msg.decode("utf-8"))
return (ann, key_vs)
+
+class SubscriberDescriptor:
+ """This describes a subscriber, for status display purposes. It contains
+ the following attributes:
+
+ .service_name: what they subscribed to (string)
+ .when: time when they subscribed (seconds since epoch)
+ .nickname: their self-provided nickname, or "?" (unicode)
+ .version: their self-provided version (string)
+ .app_versions: versions of each library they use (dict str->str)
+ .advertised_addresses: what hosts they listen on (list of strings)
+ .remote_address: the external address from which they connected (string)
+ .tubid: for subscribers connecting with Foolscap, their tubid (string)
+ """
+
+ def __init__(self, service_name, when,
+ nickname, version, app_versions,
+ advertised_addresses, remote_address, tubid):
+ self.service_name = service_name
+ self.when = when
+ self.nickname = nickname
+ self.version = version
+ self.app_versions = app_versions
+ self.advertised_addresses = advertised_addresses
+ self.remote_address = remote_address
+ self.tubid = tubid
+
+class AnnouncementDescriptor:
+ """This describes an announcement, for status display purposes. It
+ contains the following attributes, which will be empty ("" for
+ strings) if the client did not provide them:
+
+ .when: time the announcement was first received (seconds since epoch)
+ .index: the announcements 'index', a tuple of (string-or-None).
+ The server remembers one announcement per index.
+ .canary: a Referenceable on the announcer, so the server can learn
+ when they disconnect (for the status display)
+ .announcement: raw dictionary of announcement data
+ .service_name: which service they are announcing (string)
+ .version: 'my-version' portion of announcement (string)
+ .nickname: their self-provided nickname, or "" (unicode)
+
+ The following attributes will be empty ([] for lists, "" for strings)
+ unless the announcement included an 'anonymous-storage-FURL'.
+
+ .advertised_addresses: which hosts they listen on (list of strings)
+ .tubid: their tubid (string)
+ """
+
+ def __init__(self, when, index, canary, ann_d):
+ self.when = when
+ self.index = index
+ self.canary = canary
+ self.announcement = ann_d
+ self.service_name = ann_d["service-name"]
+ self.version = ann_d.get("my-version", "")
+ self.nickname = ann_d.get("nickname", u"")
+ furl = ann_d.get("anonymous-storage-FURL")
+ if furl:
+ self.advertised_addresses = rrefutil.hosts_for_furl(furl)
+ self.tubid = SturdyRef(furl).tubID
+ else:
+ self.advertised_addresses = []
+ self.tubid = ""
from allmydata.util import log, idlib, rrefutil
from foolscap.api import StringConstraint, TupleOf, SetOf, DictOf, Any, \
RemoteInterface, Referenceable, eventually, SturdyRef
+from allmydata.introducer.common import SubscriberDescriptor, \
+ AnnouncementDescriptor
FURL = StringConstraint(1000)
# We keep a copy of the old introducer (both client and server) here to
self.introducer_url = None
# 'index' is (service_name, tubid)
self._announcements = {} # dict of index -> (announcement, timestamp)
- self._subscribers = {} # dict of (rref->timestamp) dicts
+ self._subscribers = {} # [service_name]->[rref]->timestamp
self._debug_counts = {"inbound_message": 0,
"inbound_duplicate": 0,
"inbound_update": 0,
kwargs["facility"] = "tahoe.introducer"
return log.msg(*args, **kwargs)
- def get_announcements(self):
- return self._announcements
+ def get_announcements(self, include_stub_clients=True):
+ announcements = []
+ for index, (ann_t, when) in self._announcements.items():
+ (furl, service_name, ri_name, nickname, ver, oldest) = ann_t
+ if service_name == "stub_client" and not include_stub_clients:
+ continue
+ ann_d = {"nickname": nickname.decode("utf-8", "replace"),
+ "my-version": ver,
+ "service-name": service_name,
+ "anonymous-storage-FURL": furl,
+ }
+ ad = AnnouncementDescriptor(when, index, None, ann_d)
+ announcements.append(ad)
+ return announcements
+
def get_subscribers(self):
- return self._subscribers
+ s = []
+ for service_name, subscribers in self._subscribers.items():
+ for rref, when in subscribers.items():
+ tubid = rref.getRemoteTubID() or "?"
+ advertised_addresses = rrefutil.hosts_for_rref(rref)
+ remote_address = rrefutil.stringify_remote_address(rref)
+ nickname, version, app_versions = u"?", u"?", {}
+ sd = SubscriberDescriptor(service_name, when,
+ nickname, version, app_versions,
+ advertised_addresses, remote_address,
+ tubid)
+ s.append(sd)
+ return s
def remote_get_version(self):
return self.VERSION
from foolscap.api import Referenceable
import allmydata
from allmydata import node
-from allmydata.util import log
+from allmydata.util import log, rrefutil
from allmydata.introducer.interfaces import \
RIIntroducerPublisherAndSubscriberService_v2
from allmydata.introducer.common import convert_announcement_v1_to_v2, \
convert_announcement_v2_to_v1, unsign_from_foolscap, make_index, \
- get_tubid_string_from_ann
+ get_tubid_string_from_ann, SubscriberDescriptor, AnnouncementDescriptor
class IntroducerNode(node.Node):
PORTNUMFILE = "introducer.port"
"""
def __init__(self, original):
- self.original = original
+ self.original = original # also used for tests
def __eq__(self, them):
return self.original == them
def __ne__(self, them):
return self.original.getSturdyRef()
def getPeer(self):
return self.original.getPeer()
+ def getLocationHints(self):
+ return self.original.getLocationHints()
def callRemote(self, methname, *args, **kwargs):
m = getattr(self, "wrap_" + methname)
return m(*args, **kwargs)
kwargs["facility"] = "tahoe.introducer.server"
return log.msg(*args, **kwargs)
- def get_announcements(self):
- return self._announcements
+ def get_announcements(self, include_stub_clients=True):
+ """Return a list of AnnouncementDescriptor for all announcements"""
+ announcements = []
+ for (index, (_, canary, ann, when)) in self._announcements.items():
+ if ann["service-name"] == "stub_client":
+ if not include_stub_clients:
+ continue
+ ad = AnnouncementDescriptor(when, index, canary, ann)
+ announcements.append(ad)
+ return announcements
+
def get_subscribers(self):
- """Return a list of (service_name, when, subscriber_info, rref) for
- all subscribers. subscriber_info is a dict with the following keys:
- version, nickname, app-versions, my-version, oldest-supported"""
+ """Return a list of SubscriberDescriptor objects for all subscribers"""
s = []
for service_name, subscriptions in self._subscribers.items():
for rref,(subscriber_info,when) in subscriptions.items():
- s.append( (service_name, when, subscriber_info, rref) )
+ # note that if the subscriber didn't do Tub.setLocation,
+ # tubid will be None. Also, subscribers do not tell us which
+ # pubkey they use; only publishers do that.
+ tubid = rref.getRemoteTubID() or "?"
+ advertised_addresses = rrefutil.hosts_for_rref(rref)
+ remote_address = rrefutil.stringify_remote_address(rref)
+ # these three assume subscriber_info["version"]==0, but
+ # should tolerate other versions
+ if not subscriber_info:
+ # V1 clients that haven't yet sent their stub_info data
+ subscriber_info = {}
+ nickname = subscriber_info.get("nickname", u"?")
+ version = subscriber_info.get("my-version", u"?")
+ app_versions = subscriber_info.get("app-versions", {})
+ # 'when' is the time they subscribed
+ sd = SubscriberDescriptor(service_name, when,
+ nickname, version, app_versions,
+ advertised_addresses, remote_address,
+ tubid)
+ s.append(sd)
return s
def remote_get_version(self):
import simplejson
from twisted.trial import unittest
-from twisted.internet import defer
+from twisted.internet import defer, address
from twisted.python import log
from foolscap.api import Tub, Referenceable, fireEventually, flushEventualQueue
from allmydata.introducer import old
# test compatibility with old introducer .tac files
from allmydata.introducer import IntroducerNode
+from allmydata.web import introweb
from allmydata.util import pollmixin, keyutil
import allmydata.test.common_util as testutil
announcements = i.get_announcements()
self.failUnlessEqual(len(announcements), 1)
key1 = ("storage", "v0-"+keyid, None)
- self.failUnless(key1 in announcements)
- (ign, ign, ann1_out, ign) = announcements[key1]
+ self.failUnlessEqual(announcements[0].index, key1)
+ ann1_out = announcements[0].announcement
self.failUnlessEqual(ann1_out["anonymous-storage-FURL"], furl1)
furl2 = "pb://%s@127.0.0.1:36106/swissnum" % keyid
ann2 = (furl2, "storage", "RIStorage", "nick1", "ver23", "ver0")
i.remote_publish(ann2)
+ announcements = i.get_announcements()
self.failUnlessEqual(len(announcements), 2)
key2 = ("storage", None, keyid)
- self.failUnless(key2 in announcements)
- (ign, ign, ann2_out, ign) = announcements[key2]
+ wanted = [ad for ad in announcements if ad.index == key2]
+ self.failUnlessEqual(len(wanted), 1)
+ ann2_out = wanted[0].announcement
self.failUnlessEqual(ann2_out["anonymous-storage-FURL"], furl2)
d.addCallback(_then2)
return d
+NICKNAME = u"n\u00EDickname-%s" # LATIN SMALL LETTER I WITH ACUTE
class SystemTestMixin(ServiceMixin, pollmixin.PollMixin):
return self.poll(_got_announcement)
d.addCallback(_offline)
def _done(ign):
- v = list(introducer.get_announcements().values())[0]
- (ign, ign, ann1_out, ign) = v
- self.failUnlessEqual(ann1_out["anonymous-storage-FURL"], furl1)
+ v = introducer.get_announcements()[0]
+ furl = v.announcement["anonymous-storage-FURL"]
+ self.failUnlessEqual(furl, furl1)
d.addCallback(_done)
# now let the ack get back
log.msg("creating client %d: %s" % (i, tub.getShortTubID()))
if i == 0:
c = old.IntroducerClient_v1(tub, self.introducer_furl,
- u"nickname-%d" % i,
+ NICKNAME % str(i),
"version", "oldest")
else:
c = IntroducerClient(tub, self.introducer_furl,
- u"nickname-%d" % i,
+ NICKNAME % str(i),
"version", "oldest",
{"component": "component-v1"})
received_announcements[c] = {}
ann = anns[nodeid0]
nick = ann["nickname"]
self.failUnlessEqual(type(nick), unicode)
- self.failUnlessEqual(nick, u"nickname-0")
+ self.failUnlessEqual(nick, NICKNAME % "0")
if server_version == V1:
for c in publishing_clients:
cdc = c._debug_counts
]:
expected = 2
self.failUnlessEqual(cdc["outbound_message"], expected)
+ # now check the web status, make sure it renders without error
+ ir = introweb.IntroducerRoot(self.parent)
+ self.parent.nodeid = "NODEID"
+ text = ir.renderSynchronously().decode("utf-8")
+ self.failUnlessIn(NICKNAME % "0", text) # the v1 client
+ self.failUnlessIn(NICKNAME % "1", text) # a v2 client
log.msg("_check1 done")
d.addCallback(_check1)
class FakeRemoteReference:
def notifyOnDisconnect(self, *args, **kwargs): pass
def getRemoteTubID(self): return "62ubehyunnyhzs7r6vdonnm2hpi52w6y"
+ def getLocationHints(self): return [("ipv4", "here.example.com", "1234"),
+ ("ipv4", "there.example.com", "2345")]
+ def getPeer(self): return address.IPv4Address("TCP", "remote.example.com",
+ 3456)
class ClientInfo(unittest.TestCase):
def test_client_v2(self):
introducer = IntroducerService()
tub = introducer_furl = None
app_versions = {"whizzy": "fizzy"}
- client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2",
+ client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2",
"my_version", "oldest", app_versions)
#furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
#ann_s = make_ann_t(client_v2, furl1, None)
subscriber = FakeRemoteReference()
introducer.remote_subscribe_v2(subscriber, "storage",
client_v2._my_subscriber_info)
- s = introducer.get_subscribers()
- self.failUnlessEqual(len(s), 1)
- sn, when, si, rref = s[0]
- self.failUnlessIdentical(rref, subscriber)
- self.failUnlessEqual(sn, "storage")
- self.failUnlessEqual(si["version"], 0)
- self.failUnlessEqual(si["oldest-supported"], "oldest")
- self.failUnlessEqual(si["app-versions"], app_versions)
- self.failUnlessEqual(si["nickname"], u"nick-v2")
- self.failUnlessEqual(si["my-version"], "my_version")
+ subs = introducer.get_subscribers()
+ self.failUnlessEqual(len(subs), 1)
+ s0 = subs[0]
+ self.failUnlessEqual(s0.service_name, "storage")
+ self.failUnlessEqual(s0.app_versions, app_versions)
+ self.failUnlessEqual(s0.nickname, NICKNAME % u"v2")
+ self.failUnlessEqual(s0.version, "my_version")
def test_client_v1(self):
introducer = IntroducerService()
introducer.remote_subscribe(subscriber, "storage")
# the v1 subscribe interface had no subscriber_info: that was usually
# sent in a separate stub_client pseudo-announcement
- s = introducer.get_subscribers()
- self.failUnlessEqual(len(s), 1)
- sn, when, si, rref = s[0]
- # rref will be a WrapV1SubscriberInV2Interface around the real
- # subscriber
- self.failUnlessIdentical(rref.original, subscriber)
- self.failUnlessEqual(si, None) # not known yet
- self.failUnlessEqual(sn, "storage")
+ subs = introducer.get_subscribers()
+ self.failUnlessEqual(len(subs), 1)
+ s0 = subs[0]
+ self.failUnlessEqual(s0.nickname, u"?") # not known yet
+ self.failUnlessEqual(s0.service_name, "storage")
# now submit the stub_client announcement
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
ann = (furl1, "stub_client", "RIStubClient",
- u"nick-v1".encode("utf-8"), "my_version", "oldest")
+ (NICKNAME % u"v1").encode("utf-8"), "my_version", "oldest")
introducer.remote_publish(ann)
# the server should correlate the two
- s = introducer.get_subscribers()
- self.failUnlessEqual(len(s), 1)
- sn, when, si, rref = s[0]
- self.failUnlessIdentical(rref.original, subscriber)
- self.failUnlessEqual(sn, "storage")
-
- self.failUnlessEqual(si["version"], 0)
- self.failUnlessEqual(si["oldest-supported"], "oldest")
+ subs = introducer.get_subscribers()
+ self.failUnlessEqual(len(subs), 1)
+ s0 = subs[0]
+ self.failUnlessEqual(s0.service_name, "storage")
# v1 announcements do not contain app-versions
- self.failUnlessEqual(si["app-versions"], {})
- self.failUnlessEqual(si["nickname"], u"nick-v1")
- self.failUnlessEqual(si["my-version"], "my_version")
+ self.failUnlessEqual(s0.app_versions, {})
+ self.failUnlessEqual(s0.nickname, NICKNAME % u"v1")
+ self.failUnlessEqual(s0.version, "my_version")
# a subscription that arrives after the stub_client announcement
# should be correlated too
subscriber2 = FakeRemoteReference()
introducer.remote_subscribe(subscriber2, "thing2")
- s = introducer.get_subscribers()
- subs = dict([(sn, (si,rref)) for sn, when, si, rref in s])
+ subs = introducer.get_subscribers()
self.failUnlessEqual(len(subs), 2)
- (si,rref) = subs["thing2"]
- self.failUnlessIdentical(rref.original, subscriber2)
- self.failUnlessEqual(si["version"], 0)
- self.failUnlessEqual(si["oldest-supported"], "oldest")
+ s0 = [s for s in subs if s.service_name == "thing2"][0]
# v1 announcements do not contain app-versions
- self.failUnlessEqual(si["app-versions"], {})
- self.failUnlessEqual(si["nickname"], u"nick-v1")
- self.failUnlessEqual(si["my-version"], "my_version")
+ self.failUnlessEqual(s0.app_versions, {})
+ self.failUnlessEqual(s0.nickname, NICKNAME % u"v1")
+ self.failUnlessEqual(s0.version, "my_version")
class Announcements(unittest.TestCase):
def test_client_v2_unsigned(self):
introducer.remote_publish_v2(ann_s0, canary0)
a = introducer.get_announcements()
self.failUnlessEqual(len(a), 1)
- (index, (ann_s, canary, ann, when)) = a.items()[0]
- self.failUnlessIdentical(canary, canary0)
- self.failUnlessEqual(index, ("storage", None, tubid))
- self.failUnlessEqual(ann["app-versions"], app_versions)
- self.failUnlessEqual(ann["nickname"], u"nick-v2")
- self.failUnlessEqual(ann["service-name"], "storage")
- self.failUnlessEqual(ann["my-version"], "my_version")
- self.failUnlessEqual(ann["anonymous-storage-FURL"], furl1)
+ self.failUnlessIdentical(a[0].canary, canary0)
+ self.failUnlessEqual(a[0].index, ("storage", None, tubid))
+ self.failUnlessEqual(a[0].announcement["app-versions"], app_versions)
+ self.failUnlessEqual(a[0].nickname, u"nick-v2")
+ self.failUnlessEqual(a[0].service_name, "storage")
+ self.failUnlessEqual(a[0].version, "my_version")
+ self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1)
def test_client_v2_signed(self):
introducer = IntroducerService()
introducer.remote_publish_v2(ann_t0, canary0)
a = introducer.get_announcements()
self.failUnlessEqual(len(a), 1)
- (index, (ann_s, canary, ann, when)) = a.items()[0]
- self.failUnlessIdentical(canary, canary0)
- self.failUnlessEqual(index, ("storage", pks, None))
- self.failUnlessEqual(ann["app-versions"], app_versions)
- self.failUnlessEqual(ann["nickname"], u"nick-v2")
- self.failUnlessEqual(ann["service-name"], "storage")
- self.failUnlessEqual(ann["my-version"], "my_version")
- self.failUnlessEqual(ann["anonymous-storage-FURL"], furl1)
+ self.failUnlessIdentical(a[0].canary, canary0)
+ self.failUnlessEqual(a[0].index, ("storage", pks, None))
+ self.failUnlessEqual(a[0].announcement["app-versions"], app_versions)
+ self.failUnlessEqual(a[0].nickname, u"nick-v2")
+ self.failUnlessEqual(a[0].service_name, "storage")
+ self.failUnlessEqual(a[0].version, "my_version")
+ self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1)
def test_client_v1(self):
introducer = IntroducerService()
a = introducer.get_announcements()
self.failUnlessEqual(len(a), 1)
- (index, (ann_s, canary, ann, when)) = a.items()[0]
- self.failUnlessEqual(canary, None)
- self.failUnlessEqual(index, ("storage", None, tubid))
- self.failUnlessEqual(ann["app-versions"], {})
- self.failUnlessEqual(ann["nickname"], u"nick-v1".encode("utf-8"))
- self.failUnlessEqual(ann["service-name"], "storage")
- self.failUnlessEqual(ann["my-version"], "my_version")
- self.failUnlessEqual(ann["anonymous-storage-FURL"], furl1)
+ self.failUnlessEqual(a[0].index, ("storage", None, tubid))
+ self.failUnlessEqual(a[0].canary, None)
+ self.failUnlessEqual(a[0].announcement["app-versions"], {})
+ self.failUnlessEqual(a[0].nickname, u"nick-v1".encode("utf-8"))
+ self.failUnlessEqual(a[0].service_name, "storage")
+ self.failUnlessEqual(a[0].version, "my_version")
+ self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1)
class TooNewServer(IntroducerService):
-from foolscap.api import Violation, RemoteException, DeadReferenceError
+from twisted.internet import address
+from foolscap.api import Violation, RemoteException, DeadReferenceError, \
+ SturdyRef
def add_version_to_remote_reference(rref, default):
"""I try to add a .version attribute to the given RemoteReference. I call
def trap_deadref(f):
return trap_and_discard(f, DeadReferenceError)
+
+
+def hosts_for_rref(rref, ignore_localhost=True):
+ # actually, this only returns hostnames
+ advertised = []
+ for hint in rref.getLocationHints():
+ # Foolscap-0.2.5 and earlier used strings in .locationHints, but we
+ # require a newer version that uses tuples of ("ipv4", host, port)
+ assert not isinstance(hint, str), hint
+ if hint[0] == "ipv4":
+ host = hint[1]
+ if ignore_localhost and host == "127.0.0.1":
+ continue
+ advertised.append(host)
+ return advertised
+
+def hosts_for_furl(furl, ignore_localhost=True):
+ advertised = []
+ for hint in SturdyRef(furl).locationHints:
+ assert not isinstance(hint, str), hint
+ if hint[0] == "ipv4":
+ host = hint[1]
+ if ignore_localhost and host == "127.0.0.1":
+ continue
+ advertised.append(host)
+ return advertised
+
+def stringify_remote_address(rref):
+ remote = rref.getPeer()
+ if isinstance(remote, address.IPv4Address):
+ return "%s:%d" % (remote.host, remote.port)
+ # loopback is a non-IPv4Address
+ return str(remote)
from nevow import rend, inevow
from nevow.static import File as nevow_File
from nevow.util import resource_filename
-from foolscap.api import SturdyRef
-from twisted.internet import address
import allmydata
import simplejson
from allmydata import get_package_versions_string
res = {}
counts = {}
- subscribers = self.introducer_service.get_subscribers()
- for (service_name, ign, ign, ign) in subscribers:
- if service_name not in counts:
- counts[service_name] = 0
- counts[service_name] += 1
+ for s in self.introducer_service.get_subscribers():
+ if s.service_name not in counts:
+ counts[s.service_name] = 0
+ counts[s.service_name] += 1
res["subscription_summary"] = counts
announcement_summary = {}
service_hosts = {}
- for a in self.introducer_service.get_announcements().values():
- (_, _, ann, when) = a
- service_name = ann["service-name"]
+ for ad in self.introducer_service.get_announcements():
+ service_name = ad.service_name
if service_name not in announcement_summary:
announcement_summary[service_name] = 0
announcement_summary[service_name] += 1
# enough: when multiple services are run on a single host,
# they're usually either configured with the same addresses,
# or setLocationAutomatically picks up the same interfaces.
- furl = ann["anonymous-storage-FURL"]
- locations = SturdyRef(furl).getTubRef().getLocations()
- # list of tuples, ("ipv4", host, port)
- host = frozenset([hint[1]
- for hint in locations
- if hint[0] == "ipv4"])
+ host = frozenset(ad.advertised_addresses)
service_hosts[service_name].add(host)
res["announcement_summary"] = announcement_summary
distinct_hosts = dict([(name, len(hosts))
def render_announcement_summary(self, ctx, data):
services = {}
- for a in self.introducer_service.get_announcements().values():
- (_, _, ann, when) = a
- service_name = ann["service-name"]
- if service_name not in services:
- services[service_name] = 0
- services[service_name] += 1
+ for ad in self.introducer_service.get_announcements():
+ if ad.service_name not in services:
+ services[ad.service_name] = 0
+ services[ad.service_name] += 1
service_names = services.keys()
service_names.sort()
return ", ".join(["%s: %d" % (service_name, services[service_name])
def render_client_summary(self, ctx, data):
counts = {}
- clients = self.introducer_service.get_subscribers()
- for (service_name, ign, ign, ign) in clients:
- if service_name not in counts:
- counts[service_name] = 0
- counts[service_name] += 1
+ for s in self.introducer_service.get_subscribers():
+ if s.service_name not in counts:
+ counts[s.service_name] = 0
+ counts[s.service_name] += 1
return ", ".join([ "%s: %d" % (name, counts[name])
for name in sorted(counts.keys()) ] )
def data_services(self, ctx, data):
- introsvc = self.introducer_service
- services = []
- for a in introsvc.get_announcements().values():
- (_, _, ann, when) = a
- if ann["service-name"] == "stub_client":
- continue
- services.append( (when, ann) )
- services.sort(key=lambda x: (x[1]["service-name"], x[1]["nickname"]))
- # this used to be:
- #services.sort(lambda a,b: cmp( (a[1][1], a), (b[1][1], b) ) )
- # service_name was the primary key, then the whole tuple (starting
- # with the furl) was the secondary key
+ services = self.introducer_service.get_announcements(False)
+ services.sort(key=lambda ad: (ad.service_name, ad.nickname))
return services
- def render_service_row(self, ctx, (since,ann)):
- sr = SturdyRef(ann["anonymous-storage-FURL"])
- nodeid = sr.tubID
- advertised = self.show_location_hints(sr)
- ctx.fillSlots("peerid", nodeid)
- ctx.fillSlots("nickname", ann["nickname"])
- ctx.fillSlots("advertised", " ".join(advertised))
+ def render_service_row(self, ctx, ad):
+ ctx.fillSlots("peerid", ad.tubid)
+ ctx.fillSlots("nickname", ad.nickname)
+ ctx.fillSlots("advertised", " ".join(ad.advertised_addresses))
ctx.fillSlots("connected", "?")
- TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
- ctx.fillSlots("announced",
- time.strftime(TIME_FORMAT, time.localtime(since)))
- ctx.fillSlots("version", ann["my-version"])
- ctx.fillSlots("service_name", ann["service-name"])
+ when_s = time.strftime("%H:%M:%S %d-%b-%Y", time.localtime(ad.when))
+ ctx.fillSlots("announced", when_s)
+ ctx.fillSlots("version", ad.version)
+ ctx.fillSlots("service_name", ad.service_name)
return ctx.tag
def data_subscribers(self, ctx, data):
return self.introducer_service.get_subscribers()
def render_subscriber_row(self, ctx, s):
- (service_name, since, info, rref) = s
- nickname = info.get("nickname", "?")
- version = info.get("my-version", "?")
-
- sr = rref.getSturdyRef()
- # if the subscriber didn't do Tub.setLocation, nodeid will be None
- nodeid = sr.tubID or "?"
- ctx.fillSlots("peerid", nodeid)
- ctx.fillSlots("nickname", nickname)
- advertised = self.show_location_hints(sr)
- ctx.fillSlots("advertised", " ".join(advertised))
- remote_host = rref.tracker.broker.transport.getPeer()
- if isinstance(remote_host, address.IPv4Address):
- remote_host_s = "%s:%d" % (remote_host.host, remote_host.port)
- else:
- # loopback is a non-IPv4Address
- remote_host_s = str(remote_host)
- ctx.fillSlots("connected", remote_host_s)
- TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
- ctx.fillSlots("since",
- time.strftime(TIME_FORMAT, time.localtime(since)))
- ctx.fillSlots("version", version)
- ctx.fillSlots("service_name", service_name)
+ ctx.fillSlots("nickname", s.nickname)
+ ctx.fillSlots("peerid", s.tubid)
+ ctx.fillSlots("advertised", " ".join(s.advertised_addresses))
+ ctx.fillSlots("connected", s.remote_address)
+ since_s = time.strftime("%H:%M:%S %d-%b-%Y", time.localtime(s.when))
+ ctx.fillSlots("since", since_s)
+ ctx.fillSlots("version", s.version)
+ ctx.fillSlots("service_name", s.service_name)
return ctx.tag
- def show_location_hints(self, sr, ignore_localhost=True):
- advertised = []
- for hint in sr.locationHints:
- if isinstance(hint, str):
- # Foolscap-0.2.5 and earlier used strings in .locationHints
- if ignore_localhost and hint.startswith("127.0.0.1"):
- continue
- advertised.append(hint.split(":")[0])
- else:
- # Foolscap-0.2.6 and later use tuples of ("ipv4", host, port)
- if hint[0] == "ipv4":
- host = hint[1]
- if ignore_localhost and host == "127.0.0.1":
- continue
- advertised.append(hint[1])
- return advertised
-
-