Fix introweb display for mixed V1/V2 clients. Closes #1721.

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.
This commit is contained in:
Brian Warner 2012-04-23 18:02:22 -04:00
parent 080c136daf
commit 84c9f3bfb4
6 changed files with 276 additions and 183 deletions

View File

@ -1,6 +1,7 @@
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
@ -34,7 +35,7 @@ def convert_announcement_v1_to_v2(ann_t):
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,
@ -90,3 +91,67 @@ def unsign_from_foolscap(ann_t):
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 = ""

View File

@ -8,6 +8,8 @@ from allmydata.interfaces import InsufficientVersionError
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
@ -362,7 +364,7 @@ class IntroducerService_v1(service.MultiService, Referenceable):
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,
@ -380,10 +382,35 @@ class IntroducerService_v1(service.MultiService, Referenceable):
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

View File

@ -5,12 +5,12 @@ from twisted.application import service
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"
@ -56,7 +56,7 @@ class WrapV1SubscriberInV2Interface: # for_v1
"""
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):
@ -69,6 +69,8 @@ class WrapV1SubscriberInV2Interface: # for_v1
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)
@ -133,16 +135,42 @@ class IntroducerService(service.MultiService, Referenceable):
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):

View File

@ -4,7 +4,7 @@ from base64 import b32decode
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
@ -19,6 +19,7 @@ from allmydata.introducer.common import get_tubid_string_from_ann, \
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
@ -95,17 +96,19 @@ class Introducer(ServiceMixin, unittest.TestCase, pollmixin.PollMixin):
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)
@ -295,6 +298,7 @@ class Client(unittest.TestCase):
d.addCallback(_then2)
return d
NICKNAME = u"n\u00EDickname-%s" # LATIN SMALL LETTER I WITH ACUTE
class SystemTestMixin(ServiceMixin, pollmixin.PollMixin):
@ -342,9 +346,9 @@ class Queue(SystemTestMixin, unittest.TestCase):
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
@ -404,11 +408,11 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
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] = {}
@ -541,7 +545,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
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
@ -566,6 +570,12 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
]:
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)
@ -699,13 +709,17 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
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)
@ -713,16 +727,13 @@ class ClientInfo(unittest.TestCase):
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()
@ -730,50 +741,39 @@ class ClientInfo(unittest.TestCase):
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):
@ -789,14 +789,13 @@ class Announcements(unittest.TestCase):
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()
@ -813,14 +812,13 @@ class Announcements(unittest.TestCase):
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()
@ -833,14 +831,13 @@ class Announcements(unittest.TestCase):
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):

View File

@ -1,5 +1,7 @@
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
@ -23,3 +25,36 @@ def trap_and_discard(f, *errorTypes):
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)

View File

@ -3,8 +3,6 @@ import time, os
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
@ -36,18 +34,16 @@ class IntroducerRoot(rend.Page):
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
@ -60,12 +56,7 @@ class IntroducerRoot(rend.Page):
# 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))
@ -85,12 +76,10 @@ class IntroducerRoot(rend.Page):
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])
@ -98,88 +87,40 @@ class IntroducerRoot(rend.Page):
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